Compare commits

...

773 Commits

Author SHA1 Message Date
7f8fe75ab2 version 7.1 2021-10-24 14:00:11 +02:00
44143f481a Merge branch 'v7.1' 2021-10-20 22:50:35 +02:00
440abf4998 fix test to recognise inserted return statements 2021-10-20 22:50:18 +02:00
3c10427e04 Merge branch 'v7.1' 2021-10-20 22:38:23 +02:00
85956b5828 code generator: add a return (RTS) to empty subroutines. Fixes #67 2021-10-20 22:36:13 +02:00
41e40cad03 optimizer bug: don't remove empty subroutine if it's referenced. Fixes #67 2021-10-20 22:25:10 +02:00
df2d5c6585 tests for callgraph and unused subroutine removal in optimizer 2021-10-20 22:24:10 +02:00
f696fce187 Merge branch 'v7.1' 2021-10-19 23:59:07 +02:00
82d3d81bb2 don't want to complicate things by introducing a boolean literal 2021-10-19 23:58:50 +02:00
f0cff661df Merge branch 'v7.1' 2021-10-19 23:21:44 +02:00
82d20dea39 a few comment and TODO cleanups.
remove remark about chars UBYTE type, kotlin's closest native type that can contain 0-255 is a short.
2021-10-19 23:20:34 +02:00
804bb06859 clarified isInRegularRAM() by making it an extension method on AssignTarget 2021-10-19 22:36:05 +02:00
5afa7e53f8 got rid of program arg for isInRegularRAM 2021-10-19 22:30:30 +02:00
7f15b7b716 remove unneeded check for duplicate module names as this is now caught by the logic in Program.addModule itself 2021-10-19 22:12:54 +02:00
552e0c2248 rename mainModule to toplevelModule.
failed module no longer retains in the Ast.
improved some tests on that.
2021-10-19 21:49:05 +02:00
e5b9e1f5e7 string object identity hashcode can be negative sometimes, so allow a '-' character. 2021-10-19 21:08:15 +02:00
502bf90007 comments 2021-10-19 01:12:28 +02:00
40bf117497 avoid crash when parser doesn't report an offending token for a parse error 2021-10-19 00:44:33 +02:00
4011dce31b added a few more tests for the file element of Position 2021-10-19 00:26:02 +02:00
9e881e32f8 version 7.1-beta2 2021-10-16 18:47:16 +02:00
637a8899c5 Merge pull request #65 from irmen/v7.1
more V7.1 updates
2021-10-16 17:51:56 +02:00
cf0e395921 got rid of SourceCode.pathString() and the 'need' to strip < and > 2021-10-16 17:15:22 +02:00
6ef438ce50 todo 2021-10-16 15:08:36 +02:00
46e4b977a4 another attempt to fix Windows path issues 2021-10-16 15:02:15 +02:00
9626c5dead attempt to fix Windows path issue with "library:" prefixes in AsmGen 2021-10-16 14:50:08 +02:00
aea364e43d paths are now always relative to the current directory. Fixes #64 2021-10-16 14:26:33 +02:00
06defd0cb0 paths are now always relative 2021-10-16 02:43:22 +02:00
0f80897c50 todo 2021-10-15 01:02:32 +02:00
57bb1c2c0d performance optimized checks against short ranges of values 2021-10-15 00:51:45 +02:00
7b35b414e8 tweak check of DataType against multiple values 2021-10-15 00:39:42 +02:00
761aac7a23 replace inferredType.istype() by infix form 2021-10-15 00:28:23 +02:00
15a02d7664 making InferredType easier to use 2021-10-15 00:18:13 +02:00
16ed68c1ec Module.name is now derived back from the source's origin string 2021-10-14 23:58:14 +02:00
e63cf660c6 petscii now use Result instead of Either 2021-10-13 23:22:46 +02:00
aaff484306 refactor executeImportDirective 2021-10-13 23:14:27 +02:00
3281d9a215 fix error when sourcepaths is empty 2021-10-13 23:08:51 +02:00
0fcd61e00f refactor tryGetModuleFromResource 2021-10-13 23:00:22 +02:00
c4523ea470 refactor tryGetModuleFromFile 2021-10-13 22:32:52 +02:00
0447b3e4cc remove testcase that attempted to check invalid %import syntax.
we only allow unquoted names, without filename suffix, in %import.
2021-10-13 22:10:35 +02:00
4d27c2901b fix weird error printing when doing %import textio.p8 2021-10-13 21:55:51 +02:00
855e18b31c fix SourceCode to properly set the sourceName of a resource or string as well 2021-10-13 21:46:38 +02:00
d790878af6 enabled test 2021-10-13 20:28:42 +02:00
85b244df2f remove remains of %target 2021-10-13 20:13:57 +02:00
6070afa6b6 cleanup SourceCode class 2021-10-13 19:16:01 +02:00
975594703d doc 2021-10-13 18:21:48 +02:00
6b8c3ef614 renamed command line option -libdirs to -srcdirs
this more clearly separates this meaning from the internal library modules
2021-10-13 18:16:51 +02:00
9b22f05381 7.1 beta 2021-10-13 01:36:20 +02:00
ca3a990f9e todo 2021-10-13 01:33:29 +02:00
557f4f689f doc 2021-10-13 00:50:54 +02:00
66574d058a renamed InferredType.typeOrElse to getOr()
this is closer to the convention of most functional return types
2021-10-13 00:21:38 +02:00
1c7c67060d better result and error handling for importModule() 2021-10-12 23:54:48 +02:00
9827ee97ad better returnvalue/errorhandling for Petscii encoding 2021-10-12 23:26:45 +02:00
71a9a84211 don't throw basic AstException but SyntaxError instead 2021-10-12 22:30:38 +02:00
367a2a4cee cleaner return type 2021-10-12 22:21:38 +02:00
4f7465ba44 better return types 2021-10-12 21:59:19 +02:00
f891fc698c switched to more featureful Result library 2021-10-12 21:35:27 +02:00
36bec62c9a Merge branch 'master' into v7.1 2021-10-12 20:28:44 +02:00
dd5a2c8315 get rid of automated CI builds for now
the tests that actually run the compiler + assember don't work there (for now)
2021-10-12 20:27:25 +02:00
56bff46481 Update gradle.yml 2021-10-12 18:19:07 +02:00
b83a0adb19 Update gradle.yml 2021-10-12 18:17:50 +02:00
92ffefe656 create github CI action to replace travis CI 2021-10-12 18:15:12 +02:00
51b2e41879 libs updated to maven 2021-10-12 03:33:52 +02:00
ef43bc9208 lib update 2021-10-12 02:33:34 +02:00
33733a4001 improve errorhandling 2021-10-12 01:45:32 +02:00
e5a1b37981 simplify 2021-10-12 01:22:17 +02:00
30aa72dc8e fix unittest and use kotlin.test method to test for exceptions 2021-10-11 21:22:06 +02:00
2c2d474059 fix crash when attempting to import non-existing module 2021-10-11 20:37:55 +02:00
c55ac0450f unified @embedded@ and library: into the latter 2021-10-11 19:22:56 +02:00
2d26b9c994 fixed module parent linking mistakes in unit tests: module's parent should always be the GlobalNamespace 2021-10-11 01:34:55 +02:00
f38fe092ee optimized imports 2021-10-11 00:22:04 +02:00
7a33eb163b also use output path when launching emulator, fixes #61 2021-10-11 00:19:48 +02:00
5db0408b9f syntactic sugar: turned some functions into read only properties 2021-10-11 00:05:51 +02:00
3557d38ce0 cleanup: fix spelling errors and some compiler warnings/suggestions 2021-10-10 23:35:02 +02:00
7de4e9e66a exclude some more build folders from the IDE 2021-10-10 23:04:31 +02:00
73838ccb8b ref github issue 2021-10-10 23:00:31 +02:00
0509de76d5 Merge pull request #53 from meisl/testability_steps_1_2_3_again
Implement plan for testability
2021-10-10 22:30:29 +02:00
f4b3d19059 fix merge conflict 2021-10-10 22:26:18 +02:00
f37fb82d53 Merge branch 'v7.1' into testability_steps_1_2_3_again
# Conflicts:
#	compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt
#	compilerAst/src/prog8/parser/ModuleParsing.kt
#	compilerAst/test/TestAntlrParser.kt
#	parser/antlr/Prog8ANTLR.g4
2021-10-10 22:20:08 +02:00
dbe98f3fa5 remove unittest of %target directive, which is removed in 7.1 2021-10-09 18:43:18 +02:00
371d4768e6 fix filename case issue 2021-10-09 17:59:40 +02:00
562d8386ec fix antlr generator settings 2021-10-09 16:57:56 +02:00
1625e4eb85 rename prog8Parser (generated java) to Prog8ANTLRParser 2021-10-09 16:56:39 +02:00
2365a076ac clean test.p8 2021-10-09 16:33:52 +02:00
9898791771 clean test.p8 2021-10-09 16:32:44 +02:00
a1c658274d Merge pull request #50 from meisl/v7.1
*/+ fix comments & test messages, *add CompilerDevelopment.md*
2021-10-09 16:27:14 +02:00
be9998b48b Merge branch 'irmen:v7.1' into v7.1 2021-10-09 14:48:19 +02:00
e8f308f654 a few more inlinings of trivial return values 2021-10-09 01:36:13 +02:00
07132a2c42 removed unreliable inlining of non-asmsub subroutines. Fixes #60 2021-10-08 23:09:38 +02:00
9c4582e283 optimized codegen of swap of two memread values with index on the same pointer; like swap(@(ptr+i1), @(ptr+i2)) 2021-10-08 18:19:16 +02:00
0204002d9b bugfix: non-existing asm function was sometimes called to swap floats 2021-10-08 04:14:43 +02:00
b3107cfad0 Merge branch 'master' into v7.1 2021-10-04 22:38:53 +02:00
91ae68c07e blinds effects done 2021-10-04 22:15:59 +02:00
06b3bf27b5 slightly improve sys.waitvsync() on c64 2021-10-03 21:35:12 +02:00
fbef63e150 moving to raster lines via irq 2021-09-30 23:45:45 +02:00
bb8ee9bb3e Merge branch 'master' into v7.1 2021-09-28 23:27:56 +02:00
25677a4126 remove unused var 2021-09-28 23:27:32 +02:00
3aeca0a770 Merge branch 'master' into v7.1 2021-09-28 23:15:06 +02:00
4bd4733e52 fix index calc bug in palette.set_color 2021-09-28 23:12:59 +02:00
9acec4d952 changed to fixed point math to always generate bars of 32 lines height 2021-09-28 23:12:16 +02:00
8388adcd1d changed to fixed point math to always generate bars of 32 lines height 2021-09-28 22:55:55 +02:00
5988ba76b5 test example for fixed float ranges 2021-09-12 19:10:50 +02:00
1a06e7a16e expand range expression in float array decls, fixes issue #55 2021-09-12 19:02:07 +02:00
7241cef7a5 fix char range in float-range test and exclude test.p8 example from tests 2021-09-12 18:59:53 +02:00
5145296486 fix test assertion for float ranges (and re-enable test) 2021-09-12 18:53:12 +02:00
2cbf2d2226 fix regression in imported module order (reversed)
this caused an error in determining the main module and correct compilation options
2021-09-12 18:16:24 +02:00
754664aefa correctly allow codegen to proceed for byte->word register assignment. Fixes crash #58 2021-09-06 23:22:43 +02:00
af99173cd7 range expressions are on integers only 2021-09-06 22:15:27 +02:00
fd1f30f92b removed %target directive (didn't add much, too confusing, only supported single target) 2021-09-04 15:01:16 +02:00
d9ab2f8b90 upgrade to kotlin 1.5.30 2021-09-03 23:00:00 +02:00
bd6c60cf8a * improve test method names in helpers_pathsTests by means of backtick syntax 2021-08-02 15:47:42 +02:00
f0c150d93b * improve test method names in TestProg8Parser by means of backtick syntax 2021-08-02 15:36:08 +02:00
c2986eaf47 * structure TestProg8Parser with @Nested 2021-08-02 14:52:46 +02:00
ef0c4797bb Merge remote-tracking branch 'remotes/origin/v7.1' into testability_steps_1_2_3_again 2021-08-02 10:26:48 +02:00
ac02a99934 * move ModuleImporter to prog8.compiler (package & module), together with its tests 2021-08-02 10:07:19 +02:00
fb67d1155f * structure TestCompilerOnImportsAndIncludes, add (@Disabled for now) test re %import with string arg 2021-08-02 08:57:09 +02:00
eb46852bb9 * restrict access to Program.modules, add tests 2021-08-02 08:15:20 +02:00
007d8d2811 * ModuleImporter tests: refactor, more precise assertions about the program's modules 2021-08-01 17:27:41 +02:00
ebe04fc114 * @Disable ModuleImporter test re importing a faulty module twice - no easy fix for this atm 2021-08-01 16:26:27 +02:00
d7dd7f70c0 * rename file ModuleParsing.kt to ModuleImporter.kt (nothing else, still in compilerAst) 2021-08-01 15:38:21 +02:00
f2cb89a128 - ModuleImporter: deduplicate code 2021-08-01 15:37:57 +02:00
b8fade23de * (first quick) fix: ModuleImporter should look in given "libdirs" (or better "srcdirs"?) for module file 2021-08-01 15:17:47 +02:00
3b97a17648 * *little bit* of cleanup in ModuleImporter - *only refactoring* 2021-08-01 11:18:45 +02:00
0d06e3ff22 */+ refactor tests of ModuleImporter, add some tests related to libdirs issue 2021-08-01 10:48:28 +02:00
c914f7bbcf + TestCompilerOptionLibdirs.kt: libdirs option doesn't seem to work 2021-08-01 10:16:37 +02:00
1b451180c1 * test helpers assumeXyz (helpers/paths.kt) return the resulting path (unless they fail, of course); test directories are checked automatically at init, so no sanityCheckDirectories is needed anymore 2021-07-31 14:44:02 +02:00
ed061b362b * #53 step6: move IStringEncoding to prog8.compiler (package as well as module) 2021-07-30 19:25:18 +02:00
e1026584c8 * split up test helpers into separate files, move mapCombinations(..) down to compilerAst/test/helpers since they're generic and don't depend on compiler 2021-07-30 18:37:12 +02:00
4c615e4fac * solve problem re shared test helpers: a) don't use the same file name (results in same JVM class name) & b) tell gradle about it (put them in extra dir(s) test/helpers/ and add this to test source set) 2021-07-30 17:19:44 +02:00
7c9d48833b Merge branch 'irmen:v7.1' into v7.1 2021-07-22 12:14:34 +02:00
b60b195aec update junit and hamcrest unittest lib versions 2021-07-20 22:42:27 +02:00
db76c8d7f4 -/* remove IStringEncoding as param in compilerAst, and all other uses that were only because of that.
For good measure we also turn on *all* compiler tests with examples (they do take some time).
Note that the total *mentions* of IStringEncoding in the entire project went down from ~50 to 6, only 3 of which are *actual uses* (the others are 2 imports and 1 supertype ref in ICompilationTarget : IStringEncoding)!
2021-07-17 22:08:17 +02:00
de92740e87 * simple refactoring: move IStringEncoding, IMemSizer and IBuiltinFunctions to files of their own, also ext method Number.toHex to file compilerAst/src/prog8/ast/Extensions.kt. Moved the other interfaces that are indeed closely related to Node to the top of AstToplevel.kt. *This is really ONLY moving text around*, so it's easier to find things. Nothing else. 2021-07-17 21:29:01 +02:00
522bf91c30 * refactor RangeExpr, step 2: make toConstantIntegerRange and size *extension methods* and move them to compiler/astprocessing/AstExtensions.kt (along with the simple helper makeRange). They are in fact *only* used from the compiler module - strong indication that they actually belong there. 2021-07-17 21:13:34 +02:00
48d3abc1fe * refactor RangeExpr, step 1: remove IStringEncoding as ctor arg and instead put it as arg to the two methods that actually depend on it: toConstantIntegerRange and size (as *it* calls the former) 2021-07-17 20:45:17 +02:00
3f6f25e06f * @Disable tests re unsolved #55, "float[] initializer with range and no explicit array size" 2021-07-17 17:12:16 +02:00
34ba07ee3b + expose #55: float[] initializer as range where no array size is stated 2021-07-17 16:30:16 +02:00
ac37319d20 Merge branch 'bug_asmbinary' into testability_steps_1_2_3_again 2021-07-17 15:08:32 +02:00
b2c6274f74 * fix #54 / step 3: avoid some (= not all) complaints re the .binary filename 64tass still had/has.
Actually, I don't quite understand why it still says "not the real name of the file". The 64tass docs say:
> -Wno-portable
>   Don't warn about source portability problems.
>   These cross platform development annoyances are checked for:
>   * Case insensitive use of file names or use of short names.
>   * Use of backslashes for path separation instead of forward slashes.
>   * Use of reserved characters in file names.
>   * Absolute paths
2021-07-17 13:03:05 +02:00
402884b5ce * fix #54 / step 2: the path stated with assembler directive .binary must be *relative to the .asm file*, not the working directory 2021-07-17 13:02:48 +02:00
23c99002c0 * fix #54 / step 1: relativize threw IllegalArgumentException if called on non-absolute path with absolute path as argument ("different type of path") 2021-07-17 13:02:35 +02:00
ee115b3337 + expose #54, %asmbinary when outputDir != workingDir; also: refactor compiler tests on examples and add test helpers 2021-07-17 13:02:20 +02:00
82f5a141ed * reintroduce the conversion of CharLiteral to UBYTE literals, but now *during AST preprocessing*, not in the parser 2021-07-11 22:03:32 +02:00
0567168ea9 + add AST node CharLiteral, *without* turning them into ubyte s. This breaks tests, particularly 3 in TestCompilerOnCharLit. I'm comitting this separately since the failure modes might be of interest (compiler says "internal error"). 2021-07-11 21:32:18 +02:00
c80a15846d * some more housekeeping re tests: gradle doesn't like .* imports for annotations, added @Disabled comments, made warnings go away 2021-07-11 19:04:53 +02:00
5e194536a8 * refactor compiler tests, again prog8test.helpers (TODO: remove duplication) 2021-07-11 18:18:27 +02:00
43c5ab8ecc * refactor compilerAst tests, intro prog8test.helpers, @Disable the 3 tests that will pass after subsequent steps of "the plan" 2021-07-11 17:32:29 +02:00
cd295228ef + TestCompilerOnImportsAndIncludes.kt: 2 tests, both passing (but see FIXME in asmIncludeFromSameFolder.p8) 2021-07-11 15:33:44 +02:00
6c42221620 * fix AstToSourceCode: missing semicolon in header and footer, missing "@" for strings with altEncoding 2021-07-11 14:28:09 +02:00
0d73a7cd07 + add TestAstToSourceCode.kt (all 8 new tests failing due to missing semicolon) 2021-07-11 14:11:32 +02:00
39d5b7edb0 + test examples for both platforms, cx16 and c64; test two more: tehtriz and textelite (the largest ones, 20KB / 36KB) 2021-07-10 21:41:51 +02:00
6fa50a699f + add two tests for parseModule with empty source text (from File and from String) 2021-07-10 21:03:40 +02:00
ddaef3e5d5 + add tests for SourceCode.fromResources; refactor tests 2021-07-10 20:55:23 +02:00
c3e9d4a9f8 * make resources available in compilerAst/test s; *you may have to re-import the gradle project into IDEA*
Note: these resources are NOT going into the production .jar
2021-07-10 20:50:07 +02:00
7530fb67c8 + add tests for inner nodes' positions; refactor tests 2021-07-10 10:10:41 +02:00
19bb56df47 * no more scattering magic "@embedded@" all over the place: add SourceCode.isFromResources, *change Module.source from type Path to type SourceCode* 2021-07-09 17:32:33 +02:00
b0073ac933 * used "@embedded@" convention instead of "<res:...>", put it into SourceCode 2021-07-09 16:28:04 +02:00
137a89da15 * fix (hack) .name, .source and .position of Modules from the parser (via temp. subclass ParsedModule)
The temporary subclass ParsedModule : Module is introduced to concentrate all the workaround stuff in one place *while still not changing any public signature* such as of the Module ctor.
The convention used to indicate stuff from resources is still "<res:...>" not "@embedded@"- *note that this is caught by 3 tests in compiler*
2021-07-09 15:52:03 +02:00
44da7a302f + temporarily hack together a module name inside Prog8Parser.parseModule, to make the current all-too-simple import resolution work 2021-07-09 14:01:07 +02:00
4096aae8d4 * SourceCode.toString() now states both, java class and .origin 2021-07-09 13:55:56 +02:00
d3e026d82a +/* non-unique module names: provide more info, add TODO 2021-07-09 13:44:44 +02:00
fa5ecd6495 * refactor ModuleImporter: throw the proper NoSuchFileException if import isn't found, return SourceCode? from both, tryGetModuleFromResource and tryGetModuleFromFile 2021-07-09 13:44:24 +02:00
af209ad50e + intro SourceCode, tying together source code text with its *origin*; Prog8Parser now only accepts this 2021-07-09 13:24:05 +02:00
7b89228fa7 + add TODOs re ICompilationTarget 2021-07-09 13:14:02 +02:00
d31a88206c * importModule(Path): make tests pass (TODO: importLibraryModule with non-existent path) 2021-07-09 13:13:42 +02:00
cd4ed8765b + add tests for importModule(Path) with invalid path (non-existent or directory) - *failing* 2021-07-09 13:10:42 +02:00
b6f780d70d * ModuleImporter: make tests pass 2021-07-09 13:02:30 +02:00
b071a58ca7 + add tests - 4 failing in TestModuleImporter 2021-07-09 12:51:07 +02:00
ce554f7718 * rename test file 2021-07-09 12:49:55 +02:00
99b1cec2e1 */+ move ParsingFailedError to Prog8Parser.kt, intro ParseError (soon to replace ParsingFailedError), start testing proper error location info 2021-07-09 12:49:30 +02:00
46911a8905 + temporarily add PetsciiEncoding (and Petscii.kt copied from compiler) to parser; .linkParents for child nodes of Module 2021-07-09 12:31:46 +02:00
4eb61529f6 */+ rename prog8Parser (generated java) to Prog8ANTLRParser; add Kotlin class Prog8Parser to interface with it 2021-07-09 12:24:17 +02:00
81abf29bec Merge branch 'irmen:v7.1' into v7.1 2021-07-09 11:03:31 +02:00
85897ef8cd launch box16 emulator with the vice monlist file that contains symbols+breakpoints 2021-07-06 22:27:47 +02:00
b824c0b125 kotlin code style setting 2021-07-05 23:41:40 +02:00
6367c6d116 add support for second alternative emulator (box16 in case of cx16 target) 2021-07-05 22:47:51 +02:00
a7736d88a9 got rid of Module.isLibraryModule variable, is now function that derives it from source path 2021-07-04 15:44:25 +02:00
049dbf5a78 improve compiler error when defining duplicate block names 2021-07-04 15:14:39 +02:00
4ac92caeb5 Update CompilerDevelopment.md 2021-07-03 15:11:34 +02:00
7af3da2a97 Merge branch 'irmen:v7.1' into v7.1 2021-07-03 15:08:29 +02:00
95a62fcdd1 tidy up todo doc 2021-07-02 21:47:27 +02:00
7872d20554 rename spelling mistake 2021-07-02 20:58:17 +02:00
a598eb7e98 + add mention of ParseError : ParsingFailedError - particularly for testability this is something that needs to be done 2021-07-02 18:42:38 +02:00
c786acc39b + CompilerDevelopment.md, outlining what to do to improve testability (atm only for the parsing stage) 2021-07-02 15:41:38 +02:00
07d8052a57 * fix comments: no more problem with exitProcess 2021-07-02 13:28:19 +02:00
db9edb477e * less confusing assertion messages (seemingly contradictory in case of failure) 2021-07-02 12:38:24 +02:00
9bd3a6758a improve testability: use error returnvalues instead of using exitProcess() 2021-07-02 00:11:21 +02:00
2cb1560bbd Merge pull request #49 from meisl/master
A few automatic tests with examples, *of the whole process*...
2021-06-30 23:30:10 +02:00
006059438f + same warning on the other "TestCompilerXyz" file 2021-06-28 18:49:01 +02:00
84ea3b9788 + compiler/test/TestCompilerOnExamples.kt: *not actually unit tests - just a kludge!* (but better than nothing) 2021-06-28 18:42:05 +02:00
b667abde10 + compiler/test/TestCompilerOnCharLit.kt 2021-06-28 18:24:35 +02:00
aa10997171 upgrade to kotlin 1.5.20 2021-06-25 19:38:44 +02:00
7880ac1909 wording and version 2021-06-24 21:34:11 +02:00
f53848b4b9 wording and version 2021-06-24 21:25:35 +02:00
82f2f38680 Merge pull request #48 from meisl/issue40(EOF,EOL)
* fix 47, add tests
2021-06-19 01:55:44 +02:00
dcc2549574 * fix 47, add tests 2021-06-18 21:55:03 +02:00
cfe4753913 Merge pull request #45 from meisl/issue40(EOF,EOL)
Issue #40, EOF/EOL
2021-06-18 20:22:59 +02:00
fcb1a7e4d4 * #40: fix grammar rules module and block s.t. we don't need a "synthesized double EOF" (behavior remains exactly the same) 2021-06-14 22:17:30 +02:00
ce76a7dfa5 * #40: fix grammar wrt line endings - tests pass 2021-06-14 22:04:22 +02:00
7c1de81861 * #40: fix mixed line endings test, now intentionally failing (!): also test sole \r AND do not allow any recovery, neither from parser not lexer. 2021-06-14 22:02:26 +02:00
eddad20acc Merge remote-tracking branch 'remotes/origin/master' into issue40(EOF,EOL) 2021-06-13 22:56:24 +02:00
7daad57862 + #40: test for mixed (Unix/Win/Mac) line endings - *TODO: test doesn't actually fail with old grammar, but a built jar does - WHY?!* 2021-06-13 22:49:54 +02:00
1468049fe9 + #40: test that (module-level) blocks *before the last* still must have a newline after their closing } 2021-06-13 22:43:27 +02:00
3b91e59a79 * #40: refactor tests 2021-06-13 20:28:01 +02:00
3496a30528 * #40: put back in fix for EOL-after-block - tests pass 2021-06-13 20:10:35 +02:00
32bad5df15 +/* #40: add tests; temporarily undo fix for EOL-after-block so we can see that tests actually fail 2021-06-13 20:08:50 +02:00
3f58eca1be updated gradle scripts (fixed warnings), updated some library dependencies 2021-06-13 18:10:07 +02:00
2350843d1d Merge remote-tracking branch 'remotes/origin/master' into issue40(EOF,EOL) 2021-06-13 16:06:50 +02:00
a2588a178c added some simple unit tests to the ast parser 2021-06-13 14:59:57 +02:00
e5292696c4 - #40 grammar: remove obsolete note about line endings 2021-06-13 14:38:25 +02:00
34b2a65ccb Merge branch 'irmen:master' into issue40(EOF,EOL) 2021-06-12 20:29:37 +02:00
3aa3659bc7 * #40 grammar: handle different EOLs (Win, Unix, Mac) purely in grammar 2021-06-12 20:24:15 +02:00
b8117394c0 * #40 grammar: don't require EOL after blocks, so .p8 files need not end with that 2021-06-12 17:52:44 +02:00
fd2bbd2b59 no longer allow subroutine name same as its block name due to asm symbol scoping issues 2021-06-12 17:31:09 +02:00
127c470746 add some explanation about Cx16 v38 - v39 issue 2021-06-12 15:48:04 +02:00
f2844bdf1a fix crash when using labels in pointerexpression lab+index 2021-06-10 00:44:12 +02:00
c5bfef4264 slight improvement on scope doc, added doc example for %asminclude/%asmbinary 2021-06-09 23:46:07 +02:00
73863acc12 version bump 2021-06-06 10:50:05 +02:00
19e99204b9 fix asm symbol name scoping bug and add unit tests for this 2021-06-04 22:42:28 +02:00
13f5b94c3e Clarified instructions of how to obtain the compiler. Fixed sphinx css config issue. 2021-06-03 21:21:44 +02:00
3a2498401d working on unit tests for symbol scope bug 2021-06-03 21:21:38 +02:00
53b20ba625 name 2021-06-01 22:22:58 +02:00
e7f6f0950f identified asm symbol name scoping bugs 2021-06-01 22:21:50 +02:00
9fbe1b38a5 fix old block syntax in ast print routine 2021-06-01 22:08:23 +02:00
078485134d split up unittests files 2021-06-01 22:07:39 +02:00
67b1766e32 don't use ./ prefix for %asmbinary paths 2021-06-01 19:30:53 +02:00
d4b69ac79c improved repeat counter vars allocation (re-use var if possible) 2021-05-30 15:30:34 +02:00
e61a2d7083 slightly optimized repeat loop asmgen 2021-05-30 13:10:05 +02:00
c03f6604af added free words counting method to zeropage 2021-05-30 00:55:11 +02:00
572bb38ddb update to kotlin 1.5.10 2021-05-29 15:25:17 +02:00
42c5c0cb9f start of cx16 colorbars example 2021-05-26 22:13:23 +02:00
e145d2255e added palette.set_all_black() and set_all_white() 2021-05-26 21:33:18 +02:00
442fa07dd4 relax name conflict rule regarding block names vs subroutine params 2021-05-26 21:32:54 +02:00
31ae9e1243 refactor repeat loop counter var creation into single routine 2021-05-22 13:01:51 +02:00
d7f83f8df2 version bump 2021-05-20 23:38:41 +02:00
29e2d4e0c8 give error when passing invalid command line option 2021-05-20 23:34:20 +02:00
2732d2c844 exclude d64 files 2021-05-19 18:49:38 +02:00
c4a037b277 added '@shared' to syntax files 2021-05-19 18:48:18 +02:00
0e614ad6fc added @shared flag to vardecl to mark variable as shared with assembly code elsewhere, to not have it optimized away 2021-05-19 01:19:25 +02:00
ca1a8cd617 improve doc about string (im)mutability 2021-05-19 00:15:17 +02:00
ba96a637be remove strdedup compiler argument again
(string deduplication is the default again but only for known-const strings, i.e. string literals)
2021-05-18 23:52:43 +02:00
c2cac772e3 validate string interning 2021-05-18 23:37:52 +02:00
6b7216f4ec todo 2021-05-17 19:00:20 +02:00
e4fb5946dd optimize cx16 sys.wait and sys.waitvsync to use WAI instruction 2021-05-17 18:44:42 +02:00
ca61248861 printing 2-letter strings is now only optimized into direct CHROUT if it's a const string literal 2021-05-16 15:00:40 +02:00
68d7b4649e label and directive location docs 2021-05-16 12:32:08 +02:00
0416aacbbd fix %asminclude by removing scopelabel argument and improving docs to remove false promise about labels 2021-05-16 00:14:57 +02:00
bc731e6f8e fix compiler crash when taking address of label 2021-05-16 00:07:48 +02:00
ae5d7705bb allow correct parsing of source files that don't end in a EOL character. Fixes #40 2021-05-14 17:14:44 +02:00
b9bd541532 restored optimization of printing short strings into just CHROUT
but added comment about known-constness still to be resolved
2021-05-13 01:46:43 +02:00
83639c2535 code style 2021-05-13 01:00:19 +02:00
25d80f4df1 added compiler option to choose string literal deduplication yes/no -- default changed to NO 2021-05-13 00:35:22 +02:00
74f918d911 fix crashes for string encoding errors: give normal compiler error instead 2021-05-11 21:33:11 +02:00
a20efa56eb print unmappable character in escaped form in errormessage 2021-05-11 18:09:09 +02:00
f4d83075be Merge pull request #35 from meisl/master
Notepad++ syntax-file: add notes re update / alt installation
2021-05-07 21:44:58 +02:00
254592c383 Merge pull request #36 from meisl/pull36
docs: fix typo
2021-05-07 20:04:00 +02:00
ee23ac0537 * docs: fix typo 2021-05-07 15:28:22 +02:00
a48cf0bb24 + #23 Notepad++ syntax-file: add notes re update / alt installation 2021-05-07 15:12:01 +02:00
dae59238cd fix array type checking crash when attempting to use str literal to initialize a byte array.
Fixes #34
2021-05-07 00:04:29 +02:00
8736da1a21 strings of 1 and 2 length no longer optimized into one call to CHROUT - also upgrade to kotlin 1.5.0 2021-05-06 23:46:18 +02:00
09a1de69e7 Merge pull request #33 from meisl/master
+ docs: add missing word
2021-05-06 23:45:44 +02:00
63d67bc6cb + docs: add missing word 2021-05-06 15:49:58 +02:00
7099245204 Notepad++ syntax file contributor added 2021-05-05 00:39:19 +02:00
4d097d2139 Merge pull request #32 from meisl/master
syntax file for Notepad++

thanks for your contribution!!
2021-05-05 00:33:06 +02:00
6485bf9ad9 +/- #23 add test file and screenshot; fix: remove if/else as "Folding in code 2", just keywords 2021-05-04 21:55:12 +02:00
b7c5b1bfc7 * #23 rename to .md for nicer link to homepage 2021-05-04 21:34:04 +02:00
2b7546e827 + #23 syntax file for Notepad++ 2021-05-04 21:30:53 +02:00
3549ccf4b3 software license 2021-05-02 15:31:14 +02:00
e2f5752d9a add f_open_w, f_write, f_close_w to diskio to be able to save parts of memory sequentially 2021-05-01 19:13:56 +02:00
1a59019fc8 add generic error in diskio.status() if drive status can't be read 2021-05-01 15:39:39 +02:00
7bac7bdc3e more precise 2021-05-01 13:39:02 +02:00
19fe58dbac fix regression bug that left variables uninitialized 2021-05-01 01:35:03 +02:00
0a5b30e21c added fast code for x*640 2021-04-30 22:30:21 +02:00
664818fd29 try fixing a weird problem with pointervar[idx] -> memread rewriting
this was introduced in the removal of structs somehow
2021-04-30 01:34:03 +02:00
d5214e2505 fix import paths 2021-04-30 00:16:36 +02:00
d906fcea0e refactor some type checks 2021-04-30 00:09:15 +02:00
29c8e8b740 doc 2021-04-29 19:57:14 +02:00
71fec4c555 added a few more simple special codegen segements for the logic operators on a memmory-read 2021-04-29 19:38:42 +02:00
5ee36c897d todo 2021-04-29 00:57:32 +02:00
4aba0c7405 unused variables are removed more aggressively (no longer checking asm blocks for their names) 2021-04-29 00:48:16 +02:00
ed7479c854 version 7 due to removal of structs and v39 cx16 support changes 2021-04-29 00:15:54 +02:00
8d3d5f726a removed Datatype.STRUCT 2021-04-29 00:13:17 +02:00
a9a7068818 removed support for structs. It was too much hassle and complexity and subtle bugs. 2021-04-29 00:01:20 +02:00
1bde7c7718 ver 2021-04-28 20:05:56 +02:00
17068130bb removed PROG8_LIBDIR env variables and replaced with -libdirs command line option 2021-04-28 20:04:23 +02:00
81a91d62cb improved horizontal_line in highres 4c 2021-04-28 02:55:49 +02:00
2575263438 optimized gfx2.plot() for hires-4c 2021-04-28 02:49:25 +02:00
7f0e25cb50 optimized gfx2.plot() for hires-monochrome 2021-04-28 02:32:11 +02:00
a1e4e9c50f optimized gfx2.plot() for lores-256c 2021-04-28 02:22:21 +02:00
98eff2701b optimized gfx2.plot() for lores-monochrome 2021-04-28 02:15:07 +02:00
8b84f87217 removed fastrnd8() because it was hilariously bad, just use rnd() 2021-04-28 01:53:12 +02:00
306a1b7bc2 optimized gfx2.vertical_line for hires monochrome mode 2021-04-28 01:19:10 +02:00
481214c46e optimized gfx2.vertical_line for lores monochrome mode 2021-04-28 01:02:29 +02:00
a5961cbeab optimized gfx2.vertical_line for highres 4c mode 2021-04-28 00:29:21 +02:00
3bf335e0a0 todo 2021-04-27 23:13:46 +02:00
68f696d165 added 'callrom' builtin function (for cx16 target) that calls a routine in banked ROM 2021-04-25 18:04:56 +02:00
1170aed026 added 'callfar' builtin function (for cx16 target) that uses jsrfar to call a routine in banked RAM 2021-04-25 17:47:13 +02:00
bf1b2066b6 fix crashes in peekw() and pokew() 2021-04-22 18:26:46 +02:00
4c080afb76 added compiler check against impossible for loop range (unsigned downto exactly 0 with non-const startvalue and step != -1) 2021-04-21 23:03:29 +02:00
ee1c43ca91 improved scanning for return statement in routines that should return a value. 2021-04-21 20:31:29 +02:00
1c2e6f9e4c lower() and upper() now also return the lenght of the processed string. 2021-04-21 20:21:58 +02:00
dd379430d9 added docs on flexible string character mapping to petscii 2021-04-20 01:22:49 +02:00
42033ebd35 added petscii mappings for ^, _, \, {, } and | 2021-04-19 02:18:55 +02:00
a086d6e009 allow labels also in blocks instead of only in subroutines 2021-04-18 23:03:18 +02:00
c70bbdab26 fixed missing type checking in vardecl initializer values. Fixes #29
Also fix wrong assert of 0 const check in assembly gen for if-statement comparisons.
2021-04-18 22:46:21 +02:00
3d956ef554 fix wrong values for register used in array indexing expressions
added the L/H byte parts of the cx16 virtual registers
2021-04-18 13:53:02 +02:00
329f491c30 fix compiler crash with scoped const vardecls 2021-04-18 01:56:26 +02:00
e93701f50e fix compiler error when initializing var with memory(...) in block scope instead of subroutine 2021-04-17 15:49:41 +02:00
e680de05ea workaround for the joystick_get() irq problem 2021-04-15 22:56:52 +02:00
56fec674c5 actually not simplifying if-code generation, leads to larger code at the moment 2021-04-13 00:03:22 +02:00
54d92a027a fix problems with moving vardecls from inner scope to subroutine scope 2021-04-12 22:53:25 +02:00
319ac3a641 preparing optimizations for if statements 2021-04-12 03:34:58 +02:00
0a03c46351 preparing optimization plan for if statements 2021-04-12 02:37:15 +02:00
ae1b62e147 optimized integer comparison expressions some more 2021-04-12 01:23:17 +02:00
8d567f6b06 added cx16.joystick_get2() for convenience api 2021-04-12 01:07:46 +02:00
b1ef09675b fix compiler crash for some struct/array initialization assignment literals containing not just numbers 2021-04-10 00:28:32 +02:00
2b7b925090 codegen now uses correct machine target's string encoder/decoder. Encoding more robust by checking upper case mapping if lowercase mapping fails. 2021-04-09 23:33:32 +02:00
e0454e95db warn about for-loop wrapped iteration if loop range is inverted from normal 2021-04-08 22:54:47 +02:00
91e421d961 optimize x % p where p=power-of-2, into just x & (p-1) 2021-04-08 22:21:16 +02:00
c853afe769 fix compiler crash due to certain redundant typecast expressions 2021-04-08 19:45:44 +02:00
1a64cb38d5 fix compiler crash with assigning certain array values as vardecl initializer 2021-04-08 19:21:17 +02:00
ccebd22856 callgraph: mark start() also in use 2021-04-08 02:43:59 +02:00
a1f3b82333 vtui update 2021-04-08 01:36:25 +02:00
3dda29781e changed MEMTOP2 into cx16.numbanks() to query the number of RAM banks installed 2021-04-08 01:05:38 +02:00
a9d297ee31 fix inlining of sub with var that has default initialization 2021-04-08 00:35:02 +02:00
e5ff61f201 allow inlining of subroutines with parameters, and fix inlining of subroutines with variables 2021-04-07 23:38:25 +02:00
d116eb7655 paranoid, be sure to not kill carry 2021-04-06 23:55:20 +02:00
bc726c6334 optimized slow evaluation of byte-to-wordarray assignment 2021-04-06 22:50:16 +02:00
123473dfc8 cleanup 2021-04-06 00:16:29 +02:00
d9eccd4fba set correct rom banks when using floats 2021-04-05 23:21:07 +02:00
5b890847e5 make sure BASIC rom is banked in again when program exits 2021-04-05 23:12:10 +02:00
64c85b9617 fix cx16 rom v39 float changes 2021-04-05 22:54:40 +02:00
3e3b0bcd8b callgraph improved unused node checking 2021-04-05 20:45:18 +02:00
4c1eb1b12a callgraph 2021-04-05 20:32:30 +02:00
530d03d284 callgraph 2021-04-05 18:50:46 +02:00
619fa9b65e callgraph 2021-04-05 18:03:36 +02:00
0032235933 tweak to fix for windows line ending (\r\n) parse errors 2021-04-05 01:49:52 +02:00
61d1f1ea87 oops 2021-04-05 01:18:22 +02:00
238d27acdc more pleasing bob image and pattern 2021-04-05 01:14:55 +02:00
2f62271453 callgraph 2021-04-05 00:55:27 +02:00
75d5117a2d fix struct flattening parent node mismatch 2021-04-05 00:30:42 +02:00
b4700af2f5 fix windows line ending (\r\n) parse errors 2021-04-05 00:12:04 +02:00
374e2b311d refactoring unused code removal and noModification 2021-04-04 16:36:33 +02:00
49036abbaf docs 2021-04-04 12:55:29 +02:00
38ccbac97c stop after a couple of parse errors (it's not useful to continue for long if there are parse errors) 2021-04-04 12:29:56 +02:00
6b4896b8f5 doc 2021-04-02 21:28:23 +02:00
d582d1cc42 fix inlining subroutines multiple times 2021-04-02 21:23:40 +02:00
9e2b8a2aa9 fix ast node duplication/reference bug in certain optimizers 2021-04-02 19:01:46 +02:00
6fdc733941 inlining subroutines that contain variable declarations is now possible (gives a warning though) 2021-04-02 18:30:32 +02:00
422b390c48 fix ast node duplication/reference bug in certain optimizers 2021-04-02 16:56:52 +02:00
67a9d1285c some words about how the X register can't or can be used 2021-04-02 00:19:46 +02:00
8e26e38ecc fix RTS-issue with inlined return statement 2021-04-01 23:30:19 +02:00
02e12d8575 improvements for inlined subroutines: fix identifier scoping 2021-04-01 23:16:04 +02:00
fe2954ce08 todo 2021-04-01 22:10:04 +02:00
1fe4439395 fixed wrong return value when calling other subroutines in the return expression 2021-04-01 21:56:24 +02:00
2ff04d2abd cleanup 2021-04-01 19:10:55 +02:00
3f30d3aa89 added sys.waitrastborder() for c64 2021-04-01 18:53:16 +02:00
129e17b33a added sys.waitvsync() + missing documentation 2021-04-01 18:31:33 +02:00
bf2d8c3f4b update kotlin plugin to 1.4.32 2021-03-31 20:52:05 +02:00
b29f04ce01 fix unittest 2021-03-31 20:49:35 +02:00
d185ebad48 Merge pull request #27 from Elektron72/cbm-package
Move code used for all Commodore systems to new package
2021-03-31 20:34:58 +02:00
605df7c91c Move code used for all CBM systems to new package
AssemblyProgram.kt and Petscii.kt are not only used for the Commodore
64; they are also used for the Commander X16, and will likely be used
for any future Commodore systems added to Prog8. Therefore, they should
be moved to a new package containing functionality shared between these
systems.
2021-03-29 17:21:48 -04:00
ec60cad8bb commander-x16 prototype board #2 (rom v39+) address changes 2021-03-27 22:20:46 +01:00
6aa0f5a392 small optimization 2021-03-27 15:45:30 +01:00
4cae2c56ec implemented last remaining codegen for word-byte division and remainders. 2021-03-25 22:03:36 +01:00
d840975054 remove unreached error checks 2021-03-25 21:47:05 +01:00
1b14da6c03 compiler warning instead of crash when attempting to assign invalid array value to other array 2021-03-24 22:01:22 +01:00
292640b17a asmgen: string values cannot be typecasted 2021-03-24 21:49:33 +01:00
112a7b09f2 added codegen for expression that needs the status-flag register result as a value on the stack 2021-03-24 21:42:27 +01:00
863ec9ce8a Merge pull request #26 from Elektron72/vim-syntax
Add support for built-in functions to Vim syntax file (and other fixes)
2021-03-24 20:50:53 +01:00
2eb346a205 Add support for built-ins to Vim syntax file
This commit adds support for highlighting built-in functions and
variables to the Vim syntax file.
2021-03-23 19:53:20 -04:00
8092355acb Add syntax sync to Vim syntax file
This will make the highlighting slightly slower, but will fix issues
with assembly not being highlighted properly.
2021-03-23 19:41:34 -04:00
e7ef2ed31b todo 2021-03-23 23:48:53 +01:00
af4de6d2fc replacing complex array indexer expressions moved to BeforeAsmGeneration + use cx16 virtualregister instead of adding temp variables for this 2021-03-23 23:44:14 +01:00
69f73dd779 Add void operator to Vim syntax file 2021-03-23 18:12:52 -04:00
9706b46012 credits 2021-03-23 02:50:16 +01:00
6d75dd3bb8 Merge pull request #25 from Elektron72/vim-syntax
Add Vim syntax highlighting file
2021-03-23 01:29:57 +01:00
bd295ffc99 array indexer complexity is now dealt with in the asm-generator only 2021-03-22 19:40:57 +01:00
07ce3e3c9d Add Vim syntax highlighting file
The readme file in syntax-files/Vim/ was also modified to give simple
installation instructions.
2021-03-22 12:13:20 -04:00
cbc3e37a89 stuff 2021-03-22 02:29:59 +01:00
3626828ceb decided 2021-03-22 01:45:19 +01:00
24b77fb5a5 comments. 2021-03-21 21:10:29 +01:00
1505fe686a updated vtui example 2021-03-21 20:40:35 +01:00
0991131fa8 don't stript unused asmsub definitions 2021-03-21 19:55:21 +01:00
2e928bd3c2 fix compiler crash for certain str argument to asm functions 2021-03-21 18:39:39 +01:00
ca868ae19e added cx16.vload() (like the VLOAD basic instruction) 2021-03-20 02:39:53 +01:00
3e286dd14c move test 2021-03-18 19:34:54 +01:00
11247d52b1 fix bugs in word <= and >= comparisons 2021-03-18 19:20:48 +01:00
1dbc902513 fix bugs in uword <= and >= comparisons 2021-03-18 18:41:41 +01:00
330e691b78 wip 2021-03-18 02:43:08 +01:00
6780d4f562 fix bug in uword > comparison 2021-03-18 02:21:21 +01:00
b30b8b7368 fix bug in float < and > comparisons 2021-03-18 01:41:54 +01:00
3df182b8c3 created extensive comparison test suite 2021-03-18 00:50:13 +01:00
7f21d89fea moved test programs to test folder in compiler module 2021-03-17 20:15:16 +01:00
2b267b4ba1 IDE syntax 2021-03-17 19:36:37 +01:00
ef64881528 busy creating extensive comparison test suite 2021-03-17 19:35:22 +01:00
9a6bd760bd fixed issues in uword '>' 2021-03-16 23:40:32 +01:00
00b9766aea fixed issues in word '>' 2021-03-16 23:22:58 +01:00
6381d2b6ac improve word '<', word (u)word '<=' , uword '>=' codegen 2021-03-16 18:15:47 +01:00
d2ab5f230d example TODOs 2021-03-16 01:09:25 +01:00
824b41d457 improve word '>' and '>=' codegen 2021-03-16 00:48:03 +01:00
b5523c7077 don't optimize with inlining too aggressively (code bloat) 2021-03-16 00:33:15 +01:00
eb3594b18c revert to just using comparison expressions in graphics code (we're optimizing these now!) 2021-03-16 00:11:55 +01:00
852d85d010 improve uword '<' and '>' codegen 2021-03-16 00:03:51 +01:00
5e0aef04fe improve (u)byte '>=' codegen 2021-03-15 23:20:16 +01:00
a00c693f93 improve (u)byte '<=' codegen 2021-03-15 23:17:04 +01:00
c943da1448 improve ubyte '<' and '>' codegen 2021-03-15 23:12:52 +01:00
b630fae580 refactor byte '==', '!=', '<' and '>' codegen 2 2021-03-15 23:08:30 +01:00
38e40084f1 refactor byte '==', '!=', '<' and '>' codegen 2021-03-15 22:47:18 +01:00
bf23ad78e6 improve byte '<' and '>' codegen 2021-03-15 22:26:00 +01:00
ded1d19737 improve '==' and '!=' codegen 2021-03-15 19:29:32 +01:00
496a3b0d2c todo 2021-03-15 18:56:25 +01:00
6922333755 add a cmp(x,y) function that returns no value but only sets the status bits based off the comparison (can be used with a conditional jump afterwards) 2021-03-13 15:11:22 +01:00
a00c39e9cf compiler error instead of crash when using functioncall without returnvalue 2021-03-12 19:31:04 +01:00
1c1da8e38e additional optimization to the bresenham line routines 2021-03-10 18:49:40 +01:00
50a306f492 line drawing fixes 2021-03-09 22:11:30 +01:00
6995ee2d17 fix cx16 bresenham line inaccuracy 2021-03-09 22:04:19 +01:00
6c60ea9cac allocate even more c64 zeropage locations for floats 2021-03-09 21:47:36 +01:00
2431ed811a don't remove typecasts in asmsub argument lists 2021-03-09 21:29:48 +01:00
6bd205c02a fix c64 bresenham line inaccuracy 2021-03-09 21:07:55 +01:00
62ec77e148 ver 2021-03-08 23:35:52 +01:00
9120e1de88 fix ubyte/uword to float conversion crashes on Commander X16 2021-03-08 23:21:52 +01:00
60e169bd87 added optimized integer square (x*x) routine 2021-03-08 23:08:47 +01:00
e4bca5fe47 version 2021-03-06 23:07:30 +01:00
a1729b65ab fix min(), max(), sum(), abs() 2021-03-06 22:57:22 +01:00
2950d26c8e array and struct value assignments now via memcopy instead of assignment per element 2021-03-06 22:10:03 +01:00
4f8d4a9585 use memcopy to assign arrays 2021-03-06 19:01:16 +01:00
d787795759 simplified 2021-03-06 15:43:23 +01:00
cf74e73e27 IDEA syntax colors 2021-03-06 15:23:58 +01:00
2770254fd9 removed inline assembly from bobs demo 2021-03-06 14:31:26 +01:00
de04bd8cfa added more convenient number-to-string functions to conv library 2021-03-06 13:47:27 +01:00
076a547f91 added more convenient number-to-string functions to conv library 2021-03-06 13:34:57 +01:00
dffd0a2706 added fastrnd8() with the old rnd() generator code in it, new code for rnd() uses the much better rndw() generator now. 2021-03-05 22:49:14 +01:00
6c66f86103 todo 2021-03-05 21:07:35 +01:00
26502c949a add unlimited bobs example 2021-03-05 19:05:13 +01:00
8dfe510883 avoid compiler crash when evaluating const expressions fails due to things like integer out of bounds 2021-03-04 01:32:02 +01:00
96ba9f5902 spelling correction 2021-03-04 01:31:29 +01:00
3a6ba0ab71 added 'kefrenbars' example 2021-03-03 01:09:18 +01:00
32d894d6b6 optimized repeat loop for word counts 2021-02-28 21:22:46 +01:00
543efa4299 attempt 2 at optimizing repeats 2021-02-28 21:02:17 +01:00
eba0708099 Revert "optimized repeat loop for word counts"
This reverts commit 51e6bf0d
2021-02-28 20:29:28 +01:00
51e6bf0d45 optimized repeat loop for word counts 2021-02-28 17:34:18 +01:00
07b5c44a54 preparing to optimize 16 bit repeat loop 2021-02-28 17:13:15 +01:00
9fe32c1c34 codegen uses 'bra' on 65c02 instead of 'jmp' 2021-02-28 16:46:08 +01:00
0e0278c84a for loops now use 'bra' if available 2021-02-28 16:35:59 +01:00
dea775a9cd package refactor 2021-02-28 16:29:15 +01:00
7e3e18a5c7 deal with 'bra' better on 65c02 2021-02-28 16:20:03 +01:00
8e3ebc84f0 readme 2021-02-28 15:40:04 +01:00
e6079dfd71 don't always use pha/pla in pointer expression code 2021-02-27 16:21:46 +01:00
2b435fe6a5 vtui example updated to vtui 0.6 2021-02-27 03:30:21 +01:00
4e640b11fd added kernal bank switch trick to rasterbars 2021-02-26 01:16:06 +01:00
8b1e1e68fa switch to Kotlin's new JVM IR compilation 2021-02-26 01:10:00 +01:00
fd11927708 optimized highres 4c position calc a bit 2021-02-26 00:43:51 +01:00
cd500fee8c wording 2021-02-25 00:52:27 +01:00
1bd32c0f19 added animal guessing game example 2021-02-24 22:58:16 +01:00
7aefca3de0 target 2021-02-24 00:17:52 +01:00
f275ed96ea optimized palette.set_color() 2021-02-24 00:01:27 +01:00
d14dac3872 got rid of final traces of heapid, fixed compiler warnings 2021-02-24 00:01:04 +01:00
b0213b0565 vtui lib 2021-02-23 23:31:32 +01:00
c677f0a875 fixed string interning to also consider the alt-encoding 2021-02-23 23:27:44 +01:00
6e65cb2c0a added sounds to cx16 tehtriz 2021-02-23 01:29:45 +01:00
e65c5402d7 added cx16 rasterbars example 2021-02-22 02:11:44 +01:00
334f86480a added irq routines for cx16 2021-02-22 00:48:41 +01:00
0e62f5b759 don't remove subroutines in a block marked with "force_output" 2021-02-21 23:25:26 +01:00
edf9a500d3 kernel -> kernal 2021-02-21 22:48:06 +01:00
001d01fdaf slight tweak to 64tass .cpu to enable wdc65c02 variant on cx16 with its extra opcodes 2021-02-21 22:45:23 +01:00
a95677564e changed system irq/rasterirq setting routines 2021-02-21 22:23:50 +01:00
4aca8bb8df also track subroutines in the callgraph that only get their address taken 2021-02-21 22:09:49 +01:00
5540482888 compiler error for duplicate when choice labels 2021-02-21 21:26:15 +01:00
00d735249b fix pointer write outside zeropage 2021-02-21 16:22:44 +01:00
b5289511ba don't remove empty when choice from the list of choices! 2021-02-21 15:11:19 +01:00
b6ded8501f added 'align_word' and 'align_page' block options to control block start address alignment in the assembler 2021-02-21 01:24:44 +01:00
781915d2cf reducing dependencies 2021-02-20 17:54:33 +01:00
f4cef3eaf2 reducing dependencies 2021-02-20 17:19:54 +01:00
d23c2eed86 test 2021-02-20 16:58:24 +01:00
15695a304e start address of blocks without explicit memory address, is now word-aligned in memory 2021-02-20 03:06:00 +01:00
6319269976 underscore '_' is now also mapped to petscii, to the graphical symbol 2021-02-20 02:55:06 +01:00
0ed3d951a7 don't require carry parameter Pc to asmsubs to be last 2021-02-20 02:27:57 +01:00
2aa39757b4 reduce dependencies on global compilationtarget 2021-02-19 19:02:29 +01:00
39d32a3600 refactor cpuCheck 2021-02-19 18:48:12 +01:00
219d17de34 reduce dependencies on global compilaiontarget 2021-02-19 18:33:54 +01:00
9bb5b454e4 reduce dependencies on global compilaiontarget 2021-02-18 23:44:26 +01:00
2412f8c531 added cx16 vtui example 2021-02-18 23:16:38 +01:00
8701d684e6 added cx16 vtui example 2021-02-18 03:45:06 +01:00
b543cc34cd no longer warn about removing unused asmsubs 2021-02-18 01:52:56 +01:00
791dbbab9b fixed block label itself not getting the correct memory address in the assembly
fixed %asmbinary relative path issues
2021-02-18 01:28:33 +01:00
ac0b1da3fc machinedefinition doesn't import system libs itself anymore 2021-02-18 00:43:32 +01:00
2f97aedc3c fixed invalid removal of string tag from memory() 2021-02-16 23:58:31 +01:00
ab544ee965 improved string constant interning; no longer output duplicate strings in the Ast 2021-02-16 23:43:38 +01:00
fa527f8624 restored optimization of txt.print() with strings of lengths 1 or 2 2021-02-16 23:37:11 +01:00
92ee0aefee docs: replaced old invalid c64scr names with txt 2021-02-16 23:28:35 +01:00
99759ae853 enhanced tehtriz blocks to have light edges 2021-02-15 17:48:10 +01:00
81930312ff added textio.setcc2() on commanderX16 to enable setting fg+bg colors. 2021-02-15 17:47:48 +01:00
194fbcdd91 todos 2021-02-15 04:41:33 +01:00
1e3930aae2 fix bug in evaluating logical expressions if one of the operands was not boolean 1 or 0 2021-02-14 18:29:05 +01:00
62dda4d891 fix asm bug in conv.any2uword 2021-02-14 17:13:56 +01:00
2b870fb9f7 get rid of compiled examples. Just compile them yourself... 2021-02-14 17:13:29 +01:00
53f0318187 version 6.1 2021-02-14 00:07:45 +01:00
5e6e711f33 optimize pokew() 2021-02-14 00:05:57 +01:00
78af2cd4dc optimize peekw() 2021-02-13 23:52:08 +01:00
02cb237623 added poke() and pokew() builtin functions 2021-02-13 23:16:50 +01:00
cc0f19653e added peek() and peekw() builtin functions 2021-02-13 22:38:39 +01:00
4fff150c7b fixed mkword() bug 2021-02-13 22:00:13 +01:00
f6136891cc optimized for loop over const bytes, fixed downto 1 2021-02-13 13:46:02 +01:00
1e22170302 added graphical starmaps to textelite 2021-02-11 00:23:36 +01:00
bdda6f502a textelite output cleanups and alignments 2021-02-10 23:19:07 +01:00
1bbd77fddb added txt.column() 2021-02-10 22:47:49 +01:00
321fdd10d1 ported tehtriz to Cx16 2021-02-10 21:55:14 +01:00
9867dfcdeb ported tehtriz to Cx16 2021-02-10 21:44:35 +01:00
7c09ac632c got rid of the --longOptionNames in the cli argparser 2021-02-10 21:26:46 +01:00
3502f65332 reducing dependencies 2021-02-09 19:03:21 +01:00
628390c3b5 reducing dependencies 2021-02-09 18:56:47 +01:00
bc37097df2 reducing dependencies 2021-02-09 18:49:25 +01:00
7d98275763 reducing dependencies 2021-02-09 02:06:27 +01:00
d6ffb549f6 reducing dependencies 2021-02-09 01:47:05 +01:00
bcd0db984d reducing ast dependencies - moved ErrorReporter back to compiler module 2021-02-09 01:15:31 +01:00
d9244f22c2 reducing ast dependencies - separate Ast compilation module 2021-02-09 01:06:11 +01:00
c97d76dbf2 reducing ast dependencies 2021-02-09 00:05:56 +01:00
9e05e97d7f reducing ast dependencies 2021-02-07 19:38:20 +01:00
1070dedd7c todo 2021-02-07 19:08:47 +01:00
ccd1516637 reducing ast dependencies 2021-02-07 18:44:38 +01:00
f1f51a01c6 reducing ast dependencies 2021-02-07 18:34:55 +01:00
be75b8dbe5 reducing ast dependencies 2021-02-07 07:05:00 +01:00
02fae0e722 reducing ast dependencies 2021-02-07 06:50:59 +01:00
e35b739579 reducing ast dependencies 2021-02-07 06:39:08 +01:00
34aa6cc8a2 compiler checks for conflicting register usage in sub arguments vs target parameter registers 2021-02-07 05:25:50 +01:00
d7a6b20028 todo 2021-02-07 01:14:10 +01:00
eb2d5bb1f8 fix bank arg error in gfx2.position 2021-02-06 16:58:17 +01:00
cefef3d1be todo 2021-02-06 15:22:31 +01:00
cc96ab7a9b assignment source now also treats cx16.r[0-15] as registers
no longer create useless assignment code for r0=r0
2021-02-06 13:01:45 +01:00
49ea31c0a4 fix shift signed word right 2021-02-06 01:23:31 +01:00
f1478d776b fix vertical line highres 4color 2021-02-05 18:09:21 +01:00
40e4cfb686 amiga 2021-02-04 17:47:52 +01:00
76f459ee95 amiga 2021-02-02 23:09:03 +01:00
c478718019 fixed and optimized horiz_line for highres 4c 2021-02-01 22:03:10 +01:00
c27248a58b amiga 2021-01-29 23:52:29 +01:00
51bc539468 added palette.set_rgb() 2021-01-29 02:46:07 +01:00
2395863e7e asmsubs: fix clobbering and optimize register usage for loading the arguments 2021-01-29 01:52:49 +01:00
69c459c8ac gfx2 highres 4colors 2021-01-28 22:28:14 +01:00
c8855b2b10 better error msg 2021-01-27 02:40:56 +01:00
a910c0fddb gfx2 highres 4colors 2021-01-27 02:31:20 +01:00
fd55611cac asmsubs: don't use stack for simple lsb/msb/mkword arguments 2021-01-27 01:41:55 +01:00
52f6be2bb0 gfx2: changed screen mode numbering to a more intuitive sequence 2021-01-26 18:17:20 +01:00
857f930dc2 amiga 2021-01-26 00:09:42 +01:00
dd2c436dc6 tweaked repeat 2021-01-25 23:39:54 +01:00
9f047ba752 palette.set_monochrome() now has 2 arguments: screen and draw color RGB values 2021-01-24 04:15:15 +01:00
9d4ec4a9b2 syntaxfile 2021-01-24 00:42:26 +01:00
cdc6d9aa65 moved cx16 imageviewer into its own git repo. Version 6.0. 2021-01-23 23:49:17 +01:00
997bc21feb added offsetof() to get the byte offset of struct members. 2021-01-23 23:11:57 +01:00
975af4764d remove no longer needed strlen() calls from diskio routines 2021-01-23 22:46:46 +01:00
bf69219f98 allow uwordpointer[index] syntax as equivalent to @(uwordpointer+index) index can be >255 here! 2021-01-23 22:39:30 +01:00
f34f9329f1 fixed bug in memcopy 2021-01-23 19:49:53 +01:00
90271d0dcd textelite was okay 2021-01-23 19:01:02 +01:00
195cd7597d fix pointer-to-pointer assignment 2021-01-23 18:50:46 +01:00
4a81406262 fix diskio rename() and delete() 2021-01-23 17:57:30 +01:00
f9fd426843 Merge branch 'pointer-index-optimize'
# Conflicts:
#	docs/source/todo.rst
2021-01-23 15:57:23 +01:00
e612056ecd more optimal screen pointer access in plasma.p8 example 2021-01-23 15:54:18 +01:00
6f0103398b fix Y register clobbering in pointer access code 2021-01-23 15:24:41 +01:00
afb60db382 todo 2021-01-20 18:43:08 +01:00
5731b876ff textelite save bug found 2021-01-20 01:36:46 +01:00
055f917a2e fixed missing code for certain memread expressions when casted to uword 2021-01-20 01:30:11 +01:00
4ed7fb771c started pointer access optimization 2021-01-20 00:17:33 +01:00
c328e9018c cx16 assembler was moved into its own github repo 2021-01-18 01:38:33 +01:00
b270f6f713 added cx16.rombank() and rambank(). Select kernal rom in i/o heavy programs for faster disk i/o 2021-01-17 19:16:21 +01:00
5c13918f11 cx16 reset_system() bank selection change 2021-01-17 18:28:43 +01:00
40cc216557 optimize pointer var access if var is already on zeropage 2021-01-16 18:31:37 +01:00
1481f92cb0 optimize memory read expression of ptr + constant index 2021-01-16 17:41:15 +01:00
76d54fbe5c optimize assignment to memory pointer with fixed byte offset 2021-01-15 20:46:47 +01:00
9f72779cdc optimize assignment from memory pointer with fixed byte offset 2021-01-15 20:09:13 +01:00
3dcef89a74 optimize (zp),y instructions for 65c02 to use (zp) 2021-01-15 19:14:35 +01:00
46373717b6 get rid of unused ci image format reader 2021-01-15 18:29:25 +01:00
7277c08fa6 added textio.spc(). assem tweaks. 2021-01-14 22:51:09 +01:00
04e75455c4 assem tweaks 2021-01-14 21:07:06 +01:00
8ac17ae14e fix assem parsing of 4 letter instructions 2021-01-14 18:41:29 +01:00
cb5d6ddf80 readme 2021-01-13 22:48:59 +01:00
e0794db33a version 6.0beta 2021-01-13 22:41:11 +01:00
b128b79132 clearer description of memory() 2021-01-13 22:32:17 +01:00
79e6d4b8dd better check for EOF status 2021-01-13 22:11:51 +01:00
b9ddde0f12 assem 2021-01-12 03:45:18 +01:00
a0ec37b35b compiler error for missing return value 2021-01-10 16:36:08 +01:00
506ac8014c fix diskio.f_readline() that skipped first char. It also doesn't leave the end of line char in the string now. 2021-01-10 16:21:25 +01:00
72b4198301 added string.lower() / string.upper() 2021-01-10 15:29:43 +01:00
24eee0cb34 lower 2021-01-10 15:15:00 +01:00
9fc0c3f849 removed diskio.f_read_exact() - wasn't worth it over f_read() 2021-01-10 14:29:51 +01:00
db314ed903 added diskio.f_readline() 2021-01-10 05:04:56 +01:00
1ef9b8be61 assem 2021-01-10 03:44:10 +01:00
79782ad547 conv.any2uword() changed return value 2021-01-08 22:43:01 +01:00
4b6d045df1 idea syntaxfile updated 2021-01-08 16:57:16 +01:00
b4d1d545a8 introduced txt.nl() 2021-01-08 16:56:17 +01:00
f61682cdc7 moved various miscellaneous builtin functions such as exit() and progend() to sys.* 2021-01-08 16:44:34 +01:00
d61420f1c6 oops 2021-01-08 01:31:28 +01:00
3d09d605e1 moved memcopy, memset, memsetw builtin functions to sys.* 2021-01-08 01:09:37 +01:00
025dde264a move target() builtin to sys.target constant 2021-01-07 23:36:28 +01:00
87cee7a0fd check for name conflict with existing block (/module) 2021-01-07 23:28:15 +01:00
61784a03bb removed all string related builtin functions and moved them to separate routines in new 'string' library module 2021-01-07 23:10:29 +01:00
9d9ca0f08d fix bit shifting words by 8. fix type error for signed return types. 2021-01-07 22:50:40 +01:00
58f37513e7 removed all string related builtin functions and moved them to separate routines in new 'string' library module 2021-01-07 20:01:11 +01:00
ee7f9d457d text editor configs 2021-01-07 01:56:31 +01:00
bec2224c3d clearer naming 2021-01-07 01:25:50 +01:00
4305984168 assem 2021-01-06 01:03:08 +01:00
07dd64958f conv.bin2uword, conv.hex2uword, conv.str2uword, conv.str2word more robust and return parsed length in cx16.r15 2021-01-06 00:11:15 +01:00
76101d7f8d assem 2021-01-05 22:56:52 +01:00
7d6a0ab256 added conv.any2uword() 2021-01-05 22:28:46 +01:00
4309a0dc68 assem 2021-01-05 04:46:25 +01:00
dde6919446 allow when choice values to be replaced in ast (const-folding) 2021-01-05 03:49:11 +01:00
54fc9c91ac fix hole in scratch zp allocation of cx16 2021-01-05 03:48:36 +01:00
41658c97a3 assem 2021-01-05 02:49:29 +01:00
45c9cc97d9 fix invalid handling of X register functioncall result value 2021-01-05 02:44:55 +01:00
6fa7debee5 todo 2021-01-05 02:17:51 +01:00
ee9f662016 added MEMTOP2 pseudo kernal routine on cx16 to get the number of RAM banks 2021-01-05 01:48:23 +01:00
3550e1214c fix invalid handling of X register functioncall result value 2021-01-05 01:42:51 +01:00
8dcb43ad1c assem 2021-01-04 20:15:07 +01:00
e6a1442296 sys.wait() no longer resets the jiffyclock to zero 2021-01-03 02:45:25 +01:00
cb65480c6c moved wait() and reset_system() to sys block so they are now unified across c64 and cx16 2021-01-03 02:36:45 +01:00
3e7c7ab497 assem optimize 4 letter mnems for size 2021-01-03 02:17:35 +01:00
f0930d8a18 added c64.RDTIM16() utility routine to just get the lower 16 bits of the jiffy clock 2021-01-02 20:59:48 +01:00
5a846bdeb5 fixed invalid integer constant expression evaluation leading to wrong results 2021-01-02 20:33:59 +01:00
baf9dfb46c assem 2021-01-02 20:33:07 +01:00
edd3a22848 added strfind() 2021-01-02 17:49:58 +01:00
583428b19c assem 2021-01-02 15:40:36 +01:00
08d44ae553 fix compiler errors 2021-01-02 15:40:24 +01:00
b3b2541c1e assem 2021-01-01 19:25:40 +01:00
8e927e0b73 nontrivial return value evaluation now via intermediary variable to try to avoid slow stack based evaluation 2020-12-31 22:13:24 +01:00
8e3e996f4a diskio.f_open() now also checks if file exists 2020-12-31 19:27:34 +01:00
b6fa361bcc exit() now also resets the io channels. Optimized diskio data read subroutines. added diskio.f_read_all() 2020-12-31 19:09:29 +01:00
ca83092aed added large example program to check / profile compiler performance 2020-12-31 01:10:48 +01:00
3cda92331e updated dirlist 2020-12-31 01:07:37 +01:00
c989abe265 optimize ubyte -> uword type casts more 2020-12-31 01:02:36 +01:00
89230ade7a change in pattern arguments of diskio.list_files() and lf_start_list(): you can now use a simple pattern with ? and * wildcards 2020-12-30 23:34:00 +01:00
b4931c9a1f optimize horzontal_line drawing 2020-12-30 18:58:47 +01:00
ddfcf45d40 added some missing clobbers() specs 2020-12-30 16:59:31 +01:00
ee12236d53 added rect functions 2020-12-30 00:53:13 +01:00
df6698c98f fixed circle and disc geometry 2020-12-30 00:11:42 +01:00
c3b82f2cfa optimized disc() 2020-12-29 23:58:11 +01:00
64c89f1c8f fix circle and disc geometry, added rect and line routines 2020-12-29 23:52:48 +01:00
e09b65ea94 fix gfx2 vertical_line 2020-12-29 23:07:26 +01:00
c81952c356 gfx2 optimizations for vertical lines 2020-12-29 02:13:38 +01:00
f80e462d25 gfx2 optimizations for vertical lines 2020-12-29 01:36:34 +01:00
51f32677b7 gfx2 optimizations for horizontal lines, fix bug in disc drawing 2020-12-29 01:23:14 +01:00
4b366358c4 fix gfx2 color of horiz/vert lines 2020-12-28 01:33:51 +01:00
3378586098 update gradle to 6.7 2020-12-28 00:46:30 +01:00
6777d952c1 fixed crash when loopvar in for loop wasn't defined 2020-12-28 00:30:08 +01:00
6c8b18ddbd fixed crash on cx16 in word to float conversion 2020-12-28 00:19:58 +01:00
69780ecde9 fixed % operator bug 2020-12-28 00:08:22 +01:00
9e2c52e1ec added Cx16 highresbitmap example. added stippled drawing to gfx2 monochrome mode 2020-12-27 23:57:13 +01:00
6cb0e6a936 fixed lsb(value) not working when used in a comparison expression (needed to flip loading of A and Y register with the value) 2020-12-27 18:12:12 +01:00
dd82e550d5 adding rect and fillrect to gfx2 2020-12-27 17:34:25 +01:00
cdcda27d07 adding circle and disc to gfx2 2020-12-27 16:17:06 +01:00
ffffcdd50a project restructure, add experiment for httpCompilerService 2020-12-27 14:37:09 +01:00
d37d62574c project restructure 2020-12-27 07:21:39 +01:00
f2380457d6 update to new kotlin CLI parser library 2020-12-27 05:04:50 +01:00
efa42d5d96 compiler watch mode is a bit more robust now against crashes during compilation 2020-12-27 03:58:41 +01:00
e17c18b653 fix issues with memory() function, rewrite examples to use it 2020-12-27 03:35:56 +01:00
7607d3d64a check for unexecuted statements in blocks is now done for all blocks, not only main 2020-12-27 03:35:20 +01:00
d7d7147d43 added error message when not using returnvalue of a functioncall 2020-12-27 02:28:40 +01:00
b40e1eabb9 added memory() function for memory slab allocations 2020-12-27 02:28:30 +01:00
3b8e18004c fixed callgraph issue that allocated ALL variables in a (library) module even though some clearly weren't used at all. Variables declared in block level scope in a library are still all allocated / defined due to the nature of a library module with lists of definitions 2020-12-27 01:02:36 +01:00
4c03950c28 changed 'c64colors' module to 'palette' and added more general Cx16 palette manipulation routines in there. 2020-12-27 00:35:25 +01:00
170a0183f8 added 'inline' keyword to force inlining of trivial subroutines 2020-12-26 05:34:14 +01:00
c62ff16f8b added gfx2.text_charset() 2020-12-26 03:15:24 +01:00
ab495fe6e1 added gfx2.text() 2020-12-26 02:25:53 +01:00
c2a8dc23d0 R0-R15 register parameter optimization if loaded with byte instead of word 2020-12-25 22:30:40 +01:00
6734ae3c88 imageviewer now uses gfx2 for full-screen graphics. gfx2 promoted to built-in library on the cx16 target. 2020-12-25 17:57:46 +01:00
4c1c595f14 removed requirement of virtual regs R0-R15 to be at start of subroutine params 2020-12-25 15:43:48 +01:00
9002c67639 cleanup of cx16 regs lists 2020-12-25 14:00:07 +01:00
b91aabd3c0 max 16 subroutine params 2020-12-25 03:02:34 +01:00
3307f673f6 optimized cx16.vpoke etc. to be asmsubroutines instead 2020-12-24 07:12:59 +01:00
07b00bec61 fix problems with color cycling in iff viewer 2020-12-24 06:48:15 +01:00
e0d2b60d8b added diskio.f_read_exact() 2020-12-24 06:24:52 +01:00
45bfecee73 fix problems with color cycling in iff viewer 2020-12-24 05:46:57 +01:00
80e3a11268 fix faulty word[x]-- , fix invalid stz addressing modes 2020-12-24 04:08:52 +01:00
38a6c6a866 error message for too large repeat iteration count 2020-12-24 03:25:46 +01:00
8f224afed9 added color cycling support to iff viewer 2020-12-23 23:23:16 +01:00
48a4c46a6c optimized iff planar to chunky 2020-12-23 19:48:44 +01:00
7d08380c7f added cx16.vaddr() 2020-12-23 05:04:19 +01:00
b3b3cf3807 todo 2020-12-23 02:53:30 +01:00
f0f6150e18 fix problem with reuse of auto-indexer-variables that could result in wrong code for routines using multiple array indexings 2020-12-23 02:30:46 +01:00
dc600cc3ed fix crash when printing Ast for asmsubroutine with multiple return values 2020-12-23 02:03:27 +01:00
ae648b8a0a subroutines can now be defined even within regular code and will not disrupt the generated code anymore (they are moved to the end of their scope by the compiler) 2020-12-23 01:55:47 +01:00
583af3bd4f additional vpoke operations to do or,and,xor in one go without the need for a separate vpeek 2020-12-23 01:02:43 +01:00
d65cfbf093 fixed math.mul_word_40 that was actually doing *80... 2020-12-23 00:54:11 +01:00
118aed2e31 optimized code for 65c02 when setting constant 0 value 2020-12-22 17:59:47 +01:00
85abf4d123 update docs 2020-12-22 16:44:05 +01:00
44b8291540 update docs 2020-12-22 13:29:16 +01:00
d6444bba66 don't remove 'double' assignments that are actually doing something like calling a function 2020-12-22 12:52:55 +01:00
5a2f8fdfe1 asm-subroutines that ONLY return a value in the Carry or Overflow status register can now be used in an assignment to store that value. 2020-12-22 12:44:03 +01:00
bba4f84503 added target() function 2020-12-22 06:13:14 +01:00
684e081399 optimized register save/restore on Cx16 cpu target 2020-12-22 05:59:01 +01:00
96c700ee46 only save A's value if needed for a return value 2020-12-22 05:43:02 +01:00
5f15794c3b new compiled dirlist example 2020-12-22 04:58:33 +01:00
a40b3134f4 fix clobbering of A when restoring X or Y from stack 2020-12-22 04:52:46 +01:00
c70b4daf87 cleanup obsolete routine 2020-12-22 03:40:44 +01:00
928611eb20 Got rid of problematic attempts to save status register after function calls. If you really need it (for instance for if_XX instructions) it's probably better to use a short asmsub wrapper.
For function calls, register saves go via stack (to allow nested saves) for simpler cases, registers are saved in a local variable.
Fixed too agressive removal of sta-lda sequence if the lda is followed by a branching instruction.
Insert missing cmp #0 after functioncall if the value of the A register is needed in a comparison expression (could otherwise test wrong status flag)
2020-12-22 03:35:00 +01:00
f1d55c688a cx16 registers should come first in subroutine arg list 2020-12-22 00:59:07 +01:00
d22df22f7d fix examples for cx16 register syntax 2020-12-21 23:45:26 +01:00
061e1be0a4 removed ROM-float optimizations, too troublesome. Fixed LOG2 not being defined on Cx16 as well. 2020-12-21 23:22:02 +01:00
950bc4b937 cx16 virtual registers R0-R15 also available on C64 target (although in a different location in memory) 2020-12-21 21:04:29 +01:00
dcb81e6bea adding CommanderX16 virtual registers language support, rewrite cx16 examples 2020-12-21 20:38:00 +01:00
daaa83ee7d improved parsing of cpu registers (no more crash when invalid register) also adding CommanderX16 virtual registers language support 2020-12-21 19:19:53 +01:00
b7c1450121 upgrade to Antlr 4.9 2020-12-21 19:19:04 +01:00
787f52d1f8 doc 2020-12-21 18:28:10 +01:00
50213f146a undefined symbol errors are no longer reported one at a time but all at once 2020-12-21 13:03:56 +01:00
7f2aea60c9 addition 2020-12-19 03:36:52 +01:00
168621f7c2 addition 2020-12-19 03:27:08 +01:00
8b630798d8 documented the subroutine calling convention 2020-12-19 03:18:40 +01:00
52e8a44517 version 5.4 2020-12-15 22:59:02 +01:00
59f33658ad removed the rom path argument for launching the x16emu which made it fail on a non-Linux system 2020-12-15 22:51:10 +01:00
e0315bffdc decided not to change mkword() again, added note to docs about argument order 2020-12-15 22:25:06 +01:00
cd28d0c0e0 tweak 2020-12-14 21:57:16 +01:00
0baa2c8b23 fix oversight in binexpr operand swap that could result in suboptimal code 2020-12-14 21:37:40 +01:00
4977d1fbd5 bit shift expressions are "expanded" to the target value's datatype, now also for subroutine arguments.
implemented word bit shifts by variable number of bits.
2020-12-14 20:44:48 +01:00
3b7a92f1b4 adding strcopy() 2020-12-14 17:26:17 +01:00
f6920172dd image viewer tweaks 2020-12-14 15:36:15 +01:00
93bfc8f5f4 rename 2020-12-14 14:30:55 +01:00
39b7655264 imageviewer is now a single program 2020-12-14 14:30:18 +01:00
8b75ceb412 diskio.list_files now has a bigger buffer to store more filenames (around 30-40 max) 2020-12-14 14:29:42 +01:00
c39fc4010d textio.clear_screen() now uses kernal routine to clear the text screen, this also resets the cursor to top left. 2020-12-14 14:28:53 +01:00
8df778a515 fixed crash when importing modules from the same directory as the main program 2020-12-14 13:14:12 +01:00
5134ea76bf added bmp viewer 2020-12-14 02:12:26 +01:00
3ba37df29d added iff viewer 2020-12-13 19:42:30 +01:00
e221d674d9 pcxviewer done 2020-12-13 01:32:03 +01:00
251f947293 fixed parameter signature for FB_set_8_pixels_opaque() (docs are wrong) 2020-12-12 03:32:01 +01:00
41e1e1cbb0 adding pcxviewer 2020-12-12 02:40:54 +01:00
da1bc351d2 koalaviewer auto disk detect 2020-12-11 23:32:47 +01:00
43c0afdea0 fixed strlen() to work on arguments other than just a variable 2020-12-11 23:32:29 +01:00
add5bfa2ec koalaviewer scans directory for *.koa 2020-12-11 23:00:58 +01:00
34babfb5de added diskio.list_files(). ci-viewer now loads all *.ci files it finds. 2020-12-11 22:36:14 +01:00
4f6c45c86c incremental file loading 2020-12-11 21:05:03 +01:00
e6220a464c using progend() to maximize amount of mem available to load image 2020-12-10 23:52:30 +01:00
8dcd49934a added progend() builtin function 2020-12-10 23:33:45 +01:00
bedc3bdb56 allow bit shifting to be as large as the target variable's datatype 2020-12-10 22:49:27 +01:00
83ceb0fde9 optimize various simple cases for '**' (pow) like 2**x => bitshift 2020-12-10 22:37:12 +01:00
1d299c56e0 fix float '**' (pow) on cx16 2020-12-10 22:19:07 +01:00
0d735c2ccc workaround for FB_set_pixels bug 2020-12-10 21:51:32 +01:00
4094f89d4a not a bug 2020-12-10 03:22:43 +01:00
cf1e8b194a fix compiler crash for expressions of the form x = x and y (the logical booleans, not the bitwise) 2020-12-10 03:12:32 +01:00
74e5644f55 working on CI viewer 2020-12-10 03:00:37 +01:00
b5dc5fc615 added iterative file loading to diskio 2020-12-10 00:58:59 +01:00
7a7270d769 adding CI (CommanderX16 Image) file viewer 2020-12-10 00:03:47 +01:00
7549ddcd2b added TODOs for missing assignments 2020-12-10 00:03:20 +01:00
08f0303178 diskio status() now returns the status string instead of printing it 2020-12-10 00:02:21 +01:00
0d7a291b81 regenerated example disk , version 5.3 2020-12-08 23:15:31 +01:00
2265ae9600 optimized setting word values into array if index is fixed number 2020-12-08 22:54:20 +01:00
cba502e87a fixed crash when trying to assign a string literal to an array element in a string-array 2020-12-08 22:27:42 +01:00
ac94236614 fixed compiler crash when declaring a str(pointer) array without initializer 2020-12-08 22:19:11 +01:00
ddf1be2a13 status condition couldn't properly be tested because restoring the X register clobbers the status flag 2020-12-08 22:15:07 +01:00
b7694686c2 optimized code for branches containing just a goto or break statement 2020-12-08 22:00:52 +01:00
63332c0530 fix wrong branch instructions for some if_xxx 2020-12-08 21:29:40 +01:00
8a504f8eee fixed compiler crash: when passing the name of a subroutine instead of an array or string to an UWORD parameter
now allows taking the address of a subroutine &routine
2020-12-08 21:17:31 +01:00
106fc5daa4 tweak 2020-12-08 03:39:45 +01:00
7accb73993 iterative file listing instead 2020-12-08 03:34:45 +01:00
e9aa6a0956 TODOs 2020-12-08 02:20:24 +01:00
df20467e03 completed diskio file lister 2020-12-08 02:16:41 +01:00
ecbd9d739e completed diskio file lister 2020-12-08 01:34:08 +01:00
8af17c295a fixed diskio directory block sizes 2020-12-08 01:02:38 +01:00
329b28cad1 making diskio.listfiles 2020-12-07 23:49:34 +01:00
452c29574d added optimized mul 320 routine 2020-12-07 22:55:16 +01:00
5bedc1b333 remove test file 2020-12-06 18:40:47 +01:00
0bf6d2f72c tweak 2020-12-06 18:38:27 +01:00
c09b8af491 optimized koalaviewer to plot 8 pixels at once in the loop 2020-12-06 18:25:01 +01:00
260bcd3a55 added syntax error for non-constant array size declaration 2020-12-06 17:02:56 +01:00
6b5211ad12 tweak word shift unroll 2020-12-06 08:36:19 +01:00
a92ec14989 use 'stz' more often on 65c02 cpu (cx16) 2020-12-06 08:30:13 +01:00
b3348eb22b formatting 2020-12-06 07:52:58 +01:00
bec5a261e5 optimizing koalaviewer 2020-12-06 07:47:54 +01:00
4b53641e1d optimized text screen clear/fill and scrolling on c64 2020-12-06 01:16:31 +01:00
00071d53d5 optimized disc (filled circle) drawing on c64, fixed off by 1 disc width in cx16 version 2020-12-06 00:33:32 +01:00
6902834568 remove dummy argument for txt.scroll_XXXX() functions on cx16 2020-12-06 00:19:47 +01:00
fa2d87f3dd optimized disc (filled circle) drawing on cx16 2020-12-06 00:01:19 +01:00
44019d1a61 strings and arrays are no longer directly assignable to an UWORD, you need an explicit & (address-of) now 2020-12-03 18:39:32 +01:00
6f74fb49bd added c64colors module. added vpeek/vpoke to cx16 syslib. koalaviewer example now uses better c64 color palette. 2020-12-03 18:14:49 +01:00
a303b39cf0 added C64 'koala' image viewer example for Cx16 2020-12-03 16:02:51 +01:00
3e63a29c59 diskio now properly closes files after a load or save 2020-12-03 16:01:58 +01:00
261c0fc9b6 started adding syntax highlighting files 2020-12-02 20:48:50 +01:00
331 changed files with 23293 additions and 12205 deletions

3
.gitignore vendored
View File

@ -26,6 +26,9 @@ parser.out
parsetab.py
.pytest_cache/
.attach_pid*
compiler/lib/
.gradle
/prog8compiler.jar
sd*.img
*.d64

8
.idea/codeInsightSettings.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaProjectCodeInsightSettings">
<excluded-names>
<name>kotlin.Result</name>
</excluded-names>
</component>
</project>

7
.idea/compiler.xml generated Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<option name="BUILD_PROCESS_HEAP_SIZE" value="1200" />
<bytecodeTargetLevel target="11" />
</component>
</project>

View File

@ -3,14 +3,11 @@
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<Languages>
<language minSize="100" isEnabled="false" name="JavaScript" />
<language isEnabled="false" name="Groovy" />
<language isEnabled="false" name="Style Sheets" />
<language minSize="70" name="Kotlin" />
<language isEnabled="false" name="TypeScript" />
<language isEnabled="false" name="ActionScript" />
<language isEnabled="false" name="Groovy" />
</Languages>
</inspection_tool>
<inspection_tool class="PyInterpreterInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
<option name="processCode" value="false" />
<option name="processLiterals" value="true" />

2
.idea/kotlinc.xml generated
View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Kotlin2JvmCompilerArguments">
<option name="jvmTarget" value="1.8" />
<option name="jvmTarget" value="11" />
</component>
</project>

View File

@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="antlr-4.7.2-complete">
<CLASSES>
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-4.7.2-complete.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="antlr-4.8-complete">
<CLASSES>
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-4.8-complete.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

19
.idea/libraries/antlr_antlr4.xml generated Normal file
View File

@ -0,0 +1,19 @@
<component name="libraryTable">
<library name="antlr.antlr4" type="repository">
<properties maven-id="org.antlr:antlr4:4.9.2">
<exclude>
<dependency maven-id="com.ibm.icu:icu4j" />
</exclude>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.9.2/antlr4-4.9.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.9.2/antlr4-runtime-4.9.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.2/antlr-runtime-3.5.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3/ST4-4.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/javax.json/1.0.4/javax.json-1.0.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="antlr-runtime-4.7.2">
<CLASSES>
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-runtime-4.7.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="antlr-runtime-4.8">
<CLASSES>
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-runtime-4.8.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -0,0 +1,25 @@
<component name="libraryTable">
<library name="github.hypfvieh.dbus.java" type="repository">
<properties maven-id="com.github.hypfvieh:dbus-java:3.3.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/github/hypfvieh/dbus-java/3.3.0/dbus-java-3.3.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-unixsocket/0.38.5/jnr-unixsocket-0.38.5.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-ffi/2.2.1/jnr-ffi-2.2.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.1/jffi-1.3.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.1/jffi-1.3.1-native.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.0/asm-9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-commons/9.0/asm-commons-9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-analysis/9.0/asm-analysis-9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-tree/9.0/asm-tree-9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-util/9.0/asm-util-9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-a64asm/1.0.0/jnr-a64asm-1.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-x86asm/1.0.2/jnr-x86asm-1.0.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-constants/0.10.1/jnr-constants-0.10.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-enxio/0.32.3/jnr-enxio-0.32.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-posix/3.1.4/jnr-posix-3.1.4.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

10
.idea/libraries/glassfish_javax_json.xml generated Normal file
View File

@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="glassfish.javax.json" type="repository">
<properties include-transitive-deps="false" maven-id="org.glassfish:javax.json:1.1.4" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/javax.json/1.1.4/javax.json-1.1.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

10
.idea/libraries/hamcrest.xml generated Normal file
View File

@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="hamcrest" type="repository">
<properties maven-id="org.hamcrest:hamcrest:2.2" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/hamcrest/hamcrest/2.2/hamcrest-2.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="jetbrains.kotlinx.cli.jvm" type="repository">
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.3" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.3/kotlinx-cli-jvm-0.3.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

17
.idea/libraries/junit_jupiter.xml generated Normal file
View File

@ -0,0 +1,17 @@
<component name="libraryTable">
<library name="junit.jupiter" type="repository">
<properties maven-id="org.junit.jupiter:junit-jupiter:5.7.2" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter/5.7.2/junit-jupiter-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.7.2/junit-jupiter-api-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.7.2/junit-platform-commons-1.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-params/5.7.2/junit-jupiter-params-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-engine/5.7.2/junit-jupiter-engine-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.7.2/junit-platform-engine-1.7.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="kotlinx-cli-jvm-0.1.0-dev-5">
<CLASSES>
<root url="jar://$PROJECT_DIR$/compiler/lib/kotlinx-cli-jvm-0.1.0-dev-5.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -0,0 +1,15 @@
<component name="libraryTable">
<library name="michael.bull.kotlin.result.jvm" type="repository">
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/1.1.12/kotlin-result-jvm-1.1.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.10/kotlin-stdlib-jdk8-1.5.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.10/kotlin-stdlib-1.5.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.5.10/kotlin-stdlib-jdk7-1.5.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.10/kotlin-stdlib-common-1.5.10.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

11
.idea/libraries/slf4j_simple.xml generated Normal file
View File

@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="slf4j.simple" type="repository">
<properties maven-id="org.slf4j:slf4j-simple:1.7.30" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/1.7.30/slf4j-simple-1.7.30.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

13
.idea/libraries/takes.xml generated Normal file
View File

@ -0,0 +1,13 @@
<component name="libraryTable">
<library name="takes" type="repository">
<properties maven-id="org.takes:takes:1.19" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/takes/takes/1.19/takes-1.19.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/cactoos/cactoos/0.42/cactoos-0.42.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/apache/commons/commons-text/1.4/commons-text-1.4.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/apache/commons/commons-lang3/3.7/commons-lang3-3.7.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -1,10 +0,0 @@
<component name="libraryTable">
<library name="unittest-libs">
<CLASSES>
<root url="file://$PROJECT_DIR$/compiler/lib" />
</CLASSES>
<JAVADOC />
<SOURCES />
<jarDirectory url="file://$PROJECT_DIR$/compiler/lib" recursive="false" />
</library>
</component>

6
.idea/misc.xml generated
View File

@ -4,7 +4,7 @@
<option name="perGrammarGenerationSettings">
<list>
<PerGrammarGenerationSettings>
<option name="fileName" value="$PROJECT_DIR$/parser/antlr/prog8.g4" />
<option name="fileName" value="$PROJECT_DIR$/parser/antlr/Prog8ANTLR.g4" />
<option name="autoGen" value="true" />
<option name="outputDir" value="$PROJECT_DIR$/parser/src/prog8/parser" />
<option name="libDir" value="" />
@ -16,7 +16,7 @@
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="false" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
</project>

3
.idea/modules.xml generated
View File

@ -3,8 +3,11 @@
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
<module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" />
<module fileurl="file://$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" filepath="$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" />
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
<module fileurl="file://$PROJECT_DIR$/httpCompilerService/httpCompilerService.iml" filepath="$PROJECT_DIR$/httpCompilerService/httpCompilerService.iml" />
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
</modules>
</component>

View File

@ -1,11 +0,0 @@
language: java
sudo: false
# jdk: openjdk8
# dist: xenial
before_install:
- chmod +x ./gradlew
script:
- ./gradlew test

32
CompilerDevelopment.md Normal file
View File

@ -0,0 +1,32 @@
#### Just a few remarks upfront:
* There is the (gradle/IDEA) module `parser`: that's the parser generated by ANTLR4, in Java. The only file to be edited here is the grammar, `prog8.g4`.
* Then we have the module `compilerAst` - in Kotlin - which uses `parser` and adds AST nodes. Here we put our additions to the generated thing, *including any tests of the parsing stage*.
- the name is a bit misleading, as this module isn't (or, resp. shouldn't be; see below) about *compiling*, only the parsing stage
- also, the tree that comes out isn't much of an *abstraction*, but rather still more or less a parse tree (this might very well change).
- **However, let's not *yet* rename the module.** We'll find a good name during refactoring.
#### Problems with `compilerAst`:
* `ModuleImporter.kt`, doing (Prog8-) module resolution. That's not the parser's job.
* `ParsingFailedError` (in `ModuleParsing.kt`): this exception (it is actually *not* a `java.lang.Error`...) is thrown in a number of places, where other exceptions would make more sense. For example: not finding a file should just yield a `NoSuchFileException`, not this one. The other problem with it is that it does not provide any additional information about the source of parsing error, in particular a `Position`.
* During parsing, character literals are turned into UBYTEs (since there is no basic type e.g. CHAR). That's bad because it depends on a specific character encoding (`IStringEncoding` in `compilerAst/src/prog8/ast/AstToplevel.kt`) of/for some target platform. Note that *strings* are indeed encoded later, in the `compiler` module.
* The same argument applies to `IMemSizer`, and - not entirely sure about that - `IBuiltinFunctions`.
#### Steps to take, in conceptual (!) order:
1. introduce an abstraction `SourceCode` that encapsulates the origin and actual loading of Prog8 source code
- from the local file system (use case: user programs)
- from resources (prog8lib)
- from plain strings (for testing)
2. add subclass `ParseError : ParsingFailedError` which adds information about the *source of parsing error* (`SourceCode` and `Position`). We cannot just replace `ParsingFailedError` right away because it is so widely used (even in the `compiler` module). Therefore we'll just subclass for the time being, add more and more tests requiring the new one to be thrown (or, resp., NOT to be thrown), and gradually transition.
3. introduce a minimal interface to the outside, input: `SourceCode`, output: a tree with a `Module` node as the root
- this will be the Kotlin singleton `Prog8Parser` with the main method `parseModule`
- plus, optionally, method's for registering/unregistering a listener with the parser
- the *only* exception ever thrown / reported to listeners (TBD) will be `ParseError`
- anything related to the lexer, error strategies, character/token streams is hidden from the outside
- to make a clear distinction between the *generated* parser (and lexer) vs. `Prog8Parser`, and to discourage directly using the generated stuff, we'll rename the existing `prog8Parser`/`prog8Lexer` to `Prog8ANTLRParser` and `Prog8ANTLRLexer` and move them to package `prog8.parser.generated`
4. introduce AST node `CharLiteral` and keep them until after identifier resolution and type checking; insert there an AST transformation step that turns them in UBYTE constants (literals)
5. remove uses of `IStringEncoding` from module `compilerAst` - none should be necessary anymore
6. move `IStringEncoding` to module `compiler`
7. same with `ModuleImporter`, then rewrite that (addressing #46)
8. refactor AST nodes and grammar: less generated parse tree nodes (`XyzContext`), less intermediary stuff (private classes in `Antrl2Kotlin.kt`), more compact code. Also: nicer names such as simply `StringLiteral` instead of `StringLiteralValue`
9. re-think `IStringEncoding` to address #38

View File

@ -1,3 +1,10 @@
This sofware license is for Prog8 the compiler + associated libraries.
The software generated by running the compiler is excluded from this.
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007

View File

@ -1,5 +1,3 @@
[![saythanks](https://img.shields.io/badge/say-thanks-ff69b4.svg)](https://saythanks.io/to/irmen)
[![Build Status](https://travis-ci.org/irmen/prog8.svg?branch=master)](https://travis-ci.org/irmen/prog8)
[![Documentation](https://readthedocs.org/projects/prog8/badge/?version=latest)](https://prog8.readthedocs.io/)
Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors
@ -7,9 +5,6 @@ Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors
*Written by Irmen de Jong (irmen@razorvine.net)*
*Software license: GNU GPL 3.0, see file LICENSE*
This is a structured programming language for the 8-bit 6502/6510/65c02 microprocessor from the late 1970's and 1980's
as used in many home computers from that era. It is a medium to low level programming language,
which aims to provide many conveniences over raw assembly code (even when using a macro assembler).
@ -19,25 +14,36 @@ Documentation
Full documentation (syntax reference, how to use the language and the compiler, etc.) can be found at:
https://prog8.readthedocs.io/
Software license
----------------
GNU GPL 3.0, see file LICENSE
- prog8 (the compiler + libraries) is licensed under GNU GPL 3.0
- *exception:* the resulting files created by running the compiler are free to use in whatever way desired.
What does Prog8 provide?
------------------------
- big reduction of source code length over raw assembly
- reduction of source code length over raw assembly
- fast execution speed due to compilation to native assembly code. It's possible to write certain raster interrupt 'demoscene' effects purely in Prog8.
- modularity, symbol scoping, subroutines
- various data types other than just bytes (16-bit words, floats, strings)
- automatic variable allocations, automatic string and array variables and string sharing
- subroutines with an input- and output parameter signature
- no stack frame allocations because parameters and local variables are automatically allocated statically
- constant folding in expressions and other high-level program optimizations
- floating point math is supported if the target system provides floating point library routines (C64 and Cx16 both do)
- strings can contain escaped characters but also many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \, {, } and | are also accepted and converted to the closest petscii equivalents.
- automatic static variable allocations, automatic string and array variables and string sharing
- subroutines with input parameters and result values
- high-level program optimizations
- small program boilerplate/compilersupport overhead
- programs can be run multiple times without reloading because of automatic variable (re)initializations.
- conditional branches
- floating point operations (requires the C64 Basic ROM routines for this)
- 'when' statement to provide a concise jump table alternative to if/elseif chains
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse``
- structs to group together sets of variables and manipulate them at once
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse``
- various powerful built-in libraries to do I/O, number conversions, graphics and more
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
- fast execution speed due to compilation to native assembly code
- inline assembly allows you to have full control when every cycle or byte matters
- supports the sixteen 'virtual' 16-bit registers R0 - R15 from the Commander X16, and provides them also on the C64.
- encode strings and characters into petscii or screencodes as desired (C64/Cx16)
*Rapid edit-compile-run-debug cycle:*
@ -48,9 +54,9 @@ What does Prog8 provide?
*Two supported compiler targets* (contributions to improve these or to add support for other machines are welcome!):
- "c64": Commodore-64 (6510 CPU = almost a 6502), the main target.
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU) .
- If you only use standard kernel and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
- "c64": Commodore-64 (6510 CPU = almost a 6502)
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU)
- If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
@ -84,9 +90,7 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
ubyte candidate_prime = 2 ; is increased in the loop
sub start() {
; clear the sieve, to reset starting situation on subsequent runs
memset(sieve, 256, false)
; calculate primes
sys.memset(sieve, 256, false) ; clear the sieve
txt.print("prime numbers up to 255:\n\n")
ubyte amount=0
repeat {
@ -97,17 +101,17 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
txt.print(", ")
amount++
}
txt.chrout('\n')
txt.nl()
txt.print("number of primes (expected 54): ")
txt.print_ub(amount)
txt.chrout('\n')
txt.nl()
}
sub find_next_prime() -> ubyte {
while sieve[candidate_prime] {
candidate_prime++
if candidate_prime==0
return 0 ; we wrapped; no more primes available in the sieve
return 0 ; we wrapped; no more primes
}
; found next one, mark the multiples and return it.
@ -124,6 +128,7 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
when compiled an ran on a C-64 you'll get:
![c64 screen](docs/source/_static/primes_example.png)
@ -140,7 +145,8 @@ If you want to play a video game, a fully working Tetris clone is included in th
![tehtriz_screen](docs/source/_static/tehtriz.png)
The CommanderX16 compiler target is quite capable already too, here's a well known space ship
animated in 3D with hidden line removal, in the CommanderX16 emulator:
There are a couple of examples specially made for the CommanderX16 compiler target.
For instance here's a well known space ship animated in 3D with hidden line removal,
in the CommanderX16 emulator:
![cobra3d](docs/source/_static/cobra3d.png)

3
build.gradle Normal file
View File

@ -0,0 +1,3 @@
plugins {
id "org.jetbrains.kotlin.jvm" version "1.5.30" apply false
}

View File

@ -1,51 +1,53 @@
buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.20"
}
}
plugins {
// id "org.jetbrains.kotlin.jvm" version "1.4.20"
id 'application'
id 'org.jetbrains.dokka' version "0.9.18"
id 'com.github.johnrengelman.shadow' version '5.2.0'
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm"
id 'com.github.johnrengelman.shadow' version '7.1.0'
}
apply plugin: "kotlin"
apply plugin: "java"
targetCompatibility = 11
sourceCompatibility = 11
repositories {
mavenLocal()
mavenCentral()
jcenter()
maven { url "https://dl.bintray.com/orangy/maven/" }
maven { url "https://kotlin.bintray.com/kotlinx" }
}
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
dependencies {
implementation project(':parser')
implementation project(':compilerAst')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.antlr:antlr4-runtime:4.8'
implementation 'org.jetbrains.kotlinx:kotlinx-cli-jvm:0.1.0-dev-5'
// implementation 'net.razorvine:ksim65:1.6'
// implementation "com.github.hypfvieh:dbus-java:3.2.0"
implementation project(':parser')
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.3'
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2'
testImplementation 'org.hamcrest:hamcrest:2.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2'
}
configurations.all {
exclude group: 'com.ibm.icu', module: 'icu4j'
exclude group: "org.antlr", module: "antlr4"
}
configurations {
// strange antlr plugin issue, see https://github.com/gradle/gradle/issues/820
// this avoids linking in the complete antlr binary jar
compile {
extendsFrom = extendsFrom.findAll { it != configurations.antlr }
}
}
compileKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
// verbose = true
// freeCompilerArgs += "-XXLanguage:+NewInference"
}
@ -54,6 +56,7 @@ compileKotlin {
compileTestKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
}
}
@ -68,7 +71,8 @@ sourceSets {
}
test {
java {
srcDirs = ["${project.projectDir}/test"]
srcDir "${project.projectDir}/test"
srcDir "${project(':compilerAst').projectDir}/test/helpers"
}
}
}
@ -76,7 +80,7 @@ sourceSets {
startScripts.enabled = true
application {
mainClassName = 'prog8.CompilerMainKt'
mainClass = 'prog8.CompilerMainKt'
applicationName = 'p8compile'
}
@ -104,13 +108,3 @@ test {
events "skipped", "failed"
}
}
dokka {
outputFormat = 'html'
outputDirectory = "$buildDir/kdoc"
}
task wrapper(type: Wrapper) {
gradleVersion = '6.1.1'
}

View File

@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="Python" name="Python">
<configuration sdkName="Python 3.9" />
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
@ -8,12 +13,14 @@
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" />
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="parser" />
<orderEntry type="library" name="unittest-libs" level="project" />
<orderEntry type="library" name="kotlinx-cli-jvm-0.1.0-dev-5" level="project" />
<orderEntry type="library" name="antlr-runtime-4.8" level="project" />
<orderEntry type="module" module-name="compilerAst" />
<orderEntry type="library" name="Python 3.9 interpreter library" level="application" />
<orderEntry type="library" name="hamcrest" level="project" />
<orderEntry type="library" name="jetbrains.kotlinx.cli.jvm" level="project" />
<orderEntry type="library" name="junit.jupiter" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
</component>
</module>

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,20 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
max_line_length = 120
tab_width = 8
trim_trailing_whitespace = true
ij_smart_tabs = true
[*.p8]
indent_size = 4
indent_style = space
[*.asm]
indent_size = 8
indent_style = tab

View File

@ -2,6 +2,8 @@
FL_ONE_const .byte 129 ; 1.0
FL_ZERO_const .byte 0,0,0,0,0 ; 0.0
FL_LOG2_const .byte $80, $31, $72, $17, $f8 ; log(2)
floats_store_reg .byte 0 ; temp storage
@ -426,7 +428,9 @@ var_fac1_greater_f .proc
cmp #1
beq +
lda #0
+ rts
rts
+ lda #1
rts
.pend
var_fac1_greatereq_f .proc

View File

@ -4,7 +4,6 @@
;
; indent format: TABS, size=8
%target c64
%option enable_floats
floats {
@ -19,23 +18,6 @@ floats {
; note: the fac1 and fac2 are working registers and take 6 bytes each,
; floats in memory (and rom) are stored in 5-byte MFLPT packed format.
; constants in five-byte "mflpt" format in the BASIC ROM
&float FL_PIVAL = $aea8 ; 3.1415926...
&float FL_N32768 = $b1a5 ; -32768
&float FL_FONE = $b9bc ; 1
&float FL_SQRHLF = $b9d6 ; SQR(2) / 2
&float FL_SQRTWO = $b9db ; SQR(2)
&float FL_NEGHLF = $b9e0 ; -.5
&float FL_LOG2 = $b9e5 ; LOG(2)
&float FL_TENC = $baf9 ; 10
&float FL_NZMIL = $bdbd ; 1e9 (1 billion)
&float FL_FHALF = $bf11 ; .5
&float FL_LOGEB2 = $bfbf ; 1 / LOG(2)
&float FL_PIHALF = $e2e0 ; PI / 2
&float FL_TWOPI = $e2e5 ; 2 * PI
&float FL_FR4 = $e2ea ; .25
; oddly enough, 0.0 isn't available in the kernel.
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
@ -100,7 +82,7 @@ romsub $bc58 = ABS() ; fac1 = ABS(fac1)
romsub $bf71 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
romsub $bf74 = SQRA() clobbers(A,X,Y) ; fac1 = SQRT(fac2)
romsub $bfed = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
romsub $bfb4 = NEGOP() clobbers(A) ; switch the sign of fac1
romsub $bfb4 = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
romsub $e097 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
romsub $e264 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
romsub $e26b = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
@ -212,7 +194,7 @@ sub print_f (float value) {
}}
}
%asminclude "library:c64/floats.asm", ""
%asminclude "library:c64/floats_funcs.asm", ""
%asminclude "library:c64/floats.asm"
%asminclude "library:c64/floats_funcs.asm"
}

View File

@ -175,8 +175,8 @@ func_log2_fac1 .proc
stx P8ZP_SCRATCH_REG
jsr LOG
jsr MOVEF
lda #<FL_LOG2
ldy #>FL_LOG2
lda #<FL_LOG2_const
ldy #>FL_LOG2_const
jsr MOVFM
jsr FDIVT
ldx P8ZP_SCRATCH_REG

View File

@ -1,8 +1,7 @@
%target c64
%import textio
; bitmap pixel graphics module for the C64
; only black/white monchrome 320x200 for now
; only black/white monochrome 320x200 for now
; assumes bitmap screen memory is $2000-$3fff
graphics {
@ -27,29 +26,40 @@ graphics {
}
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
memset(BITMAP_ADDRESS, 320*200/8, 0)
sys.memset(BITMAP_ADDRESS, 320*200/8, 0)
txt.fill_screen(pixelcolor << 4 | bgcolor, 0)
}
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
; Bresenham algorithm.
; This code special cases various quadrant loops to allow simple ++ and -- operations.
; TODO rewrite this in optimized assembly
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
if y1>y2 {
; make sure dy is always positive to avoid 8 instead of just 4 special cases
; make sure dy is always positive to have only 4 instead of 8 special cases
swap(x1, x2)
swap(y1, y2)
}
word @zp dx = (x2 as word)-x1
word @zp dy = (y2 as word)-y1
if dx==0 {
vertical_line(x1, y1, abs(dy) as ubyte +1)
return
}
if dy==0 {
if x1>x2
x1=x2
horizontal_line(x1, y1, abs(dx) as uword +1)
return
}
word @zp d = 0
ubyte positive_ix = true
word @zp dx = x2-x1 as word
word @zp dy = y2-y1
if dx < 0 {
dx = -dx
positive_ix = false
}
dx *= 2
dy *= 2
word @zp dx2 = dx*2
word @zp dy2 = dy*2
internal_plotx = x1
if dx >= dy {
@ -59,10 +69,10 @@ graphics {
if internal_plotx==x2
return
internal_plotx++
d += dy
d += dy2
if d > dx {
y1++
d -= dx
d -= dx2
}
}
} else {
@ -71,10 +81,10 @@ graphics {
if internal_plotx==x2
return
internal_plotx--
d += dy
d += dy2
if d > dx {
y1++
d -= dx
d -= dx2
}
}
}
@ -86,10 +96,10 @@ graphics {
if y1 == y2
return
y1++
d += dx
d += dx2
if d > dy {
internal_plotx++
d -= dy
d -= dy2
}
}
} else {
@ -98,88 +108,183 @@ graphics {
if y1 == y2
return
y1++
d += dx
d += dx2
if d > dy {
internal_plotx--
d -= dy
d -= dy2
}
}
}
}
}
sub rect(uword x, ubyte y, uword width, ubyte height) {
if width==0 or height==0
return
horizontal_line(x, y, width)
if height==1
return
horizontal_line(x, y+height-1, width)
vertical_line(x, y+1, height-2)
if width==1
return
vertical_line(x+width-1, y+1, height-2)
}
sub fillrect(uword x, ubyte y, uword width, ubyte height) {
if width==0
return
repeat height {
horizontal_line(x, y, width)
y++
}
}
sub horizontal_line(uword x, ubyte y, uword length) {
if length<8 {
internal_plotx=x
repeat lsb(length) {
internal_plot(y)
internal_plotx++
}
return
}
ubyte separate_pixels = lsb(x) & 7
uword addr = get_y_lookup(y) + (x&$fff8)
if separate_pixels {
%asm {{
lda addr
sta P8ZP_SCRATCH_W1
lda addr+1
sta P8ZP_SCRATCH_W1+1
ldy separate_pixels
lda _filled_right,y
eor #255
ldy #0
ora (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W1),y
}}
addr += 8
length += separate_pixels
length -= 8
}
if length {
%asm {{
lda length
and #7
sta separate_pixels
stx P8ZP_SCRATCH_REG
lsr length+1
ror length
lsr length+1
ror length
lsr length+1
ror length
lda addr
sta _modified+1
lda addr+1
sta _modified+2
lda length
ora length+1
beq _zero
ldy length
ldx #$ff
_modified stx $ffff ; modified
lda _modified+1
clc
adc #8
sta _modified+1
bcc +
inc _modified+2
+ dey
bne _modified
_zero ldx P8ZP_SCRATCH_REG
ldy separate_pixels
beq _zero2
lda _modified+1
sta P8ZP_SCRATCH_W1
lda _modified+2
sta P8ZP_SCRATCH_W1+1
lda _filled_right,y
ldy #0
ora (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W1),y
jmp _zero2
_filled_right .byte 0, %10000000, %11000000, %11100000, %11110000, %11111000, %11111100, %11111110
_zero2
}}
}
}
sub vertical_line(uword x, ubyte y, ubyte height) {
internal_plotx = x
repeat height {
internal_plot(y)
y++
}
}
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
; Midpoint algorithm
if radius==0
return
ubyte @zp ploty
ubyte @zp xx = radius
ubyte @zp yy = 0
byte @zp decisionOver2 = 1-xx as byte
word @zp decisionOver2 = (1 as word)-radius
while xx>=yy {
internal_plotx = xcenter + xx
while radius>=yy {
internal_plotx = xcenter + radius
ploty = ycenter + yy
internal_plot(ploty)
internal_plotx = xcenter - xx
internal_plotx = xcenter - radius
internal_plot(ploty)
internal_plotx = xcenter + xx
internal_plotx = xcenter + radius
ploty = ycenter - yy
internal_plot(ploty)
internal_plotx = xcenter - xx
internal_plotx = xcenter - radius
internal_plot(ploty)
internal_plotx = xcenter + yy
ploty = ycenter + xx
ploty = ycenter + radius
internal_plot(ploty)
internal_plotx = xcenter - yy
internal_plot(ploty)
internal_plotx = xcenter + yy
ploty = ycenter - xx
ploty = ycenter - radius
internal_plot(ploty)
internal_plotx = xcenter - yy
internal_plot(ploty)
yy++
if decisionOver2<=0
decisionOver2 += 2*yy+1
decisionOver2 += (yy as word)*2+1
else {
xx--
decisionOver2 += 2*(yy-xx)+1
radius--
decisionOver2 += (yy as word -radius)*2+1
}
}
}
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
; Midpoint algorithm, filled
ubyte xx = radius
ubyte yy = 0
byte decisionOver2 = 1-xx as byte
if radius==0
return
ubyte @zp yy = 0
word decisionOver2 = (1 as word)-radius
while xx>=yy {
ubyte ycenter_plus_yy = ycenter + yy
ubyte ycenter_min_yy = ycenter - yy
ubyte ycenter_plus_xx = ycenter + xx
ubyte ycenter_min_xx = ycenter - xx
for internal_plotx in xcenter to xcenter+xx {
internal_plot(ycenter_plus_yy)
internal_plot(ycenter_min_yy)
}
for internal_plotx in xcenter-xx to xcenter-1 {
internal_plot(ycenter_plus_yy)
internal_plot(ycenter_min_yy)
}
for internal_plotx in xcenter to xcenter+yy {
internal_plot(ycenter_plus_xx)
internal_plot(ycenter_min_xx)
}
for internal_plotx in xcenter-yy to xcenter {
internal_plot(ycenter_plus_xx)
internal_plot(ycenter_min_xx)
}
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*2+1)
horizontal_line(xcenter-radius, ycenter-yy, radius*2+1)
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
yy++
if decisionOver2<=0
decisionOver2 += 2*yy+1
decisionOver2 += (yy as word)*2+1
else {
xx--
decisionOver2 += 2*(yy-xx)+1
radius--
decisionOver2 += (yy as word -radius)*2+1
}
}
}
@ -192,11 +297,11 @@ graphics {
; @(addr) |= ormask[lsb(px) & 7]
; }
asmsub plot(uword plotx @XY, ubyte ploty @A) clobbers (A, X, Y) {
inline asmsub plot(uword plotx @XY, ubyte ploty @A) clobbers (A, X, Y) {
%asm {{
stx internal_plotx
sty internal_plotx+1
jmp internal_plot
stx graphics.internal_plotx
sty graphics.internal_plotx+1
jsr graphics.internal_plot
}}
}
@ -248,6 +353,17 @@ _y_lookup_hi .byte >_plot_y_values
}}
}
asmsub get_y_lookup(ubyte y @Y) -> uword @AY {
%asm {{
lda internal_plot._y_lookup_lo,y
pha
lda internal_plot._y_lookup_hi,y
tay
pla
rts
}}
}
}

View File

@ -5,13 +5,11 @@
;
; indent format: TABS, size=8
%target c64
c64 {
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
&ubyte TIME_MID = $a1 ; .. mid byte
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
&ubyte STATUS = $90 ; kernel status variable for I/O
&ubyte STATUS = $90 ; kernal status variable for I/O
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
@ -178,7 +176,7 @@ c64 {
; ---- C64 ROM kernal routines ----
romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use c64scr.print instead)
romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use txt.print instead)
romsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen
romsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
romsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
@ -202,7 +200,7 @@ romsub $FFAE = UNLSN() clobbers(A) ; command serial
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
@ -211,10 +209,10 @@ romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
@ -225,6 +223,41 @@ romsub $FFF3 = IOBASE() -> uword @ XY ; read base addr
; ---- end of C64 ROM kernal routines ----
; ---- utilities -----
asmsub STOP2() -> ubyte @A {
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
%asm {{
txa
pha
jsr c64.STOP
beq +
pla
tax
lda #0
rts
+ pla
tax
lda #1
rts
}}
}
asmsub RDTIM16() -> uword @AY {
; -- like RDTIM() but only returning the lower 16 bits in AY for convenience
%asm {{
stx P8ZP_SCRATCH_REG
jsr c64.RDTIM
pha
txa
tay
pla
ldx P8ZP_SCRATCH_REG
rts
}}
}
; ---- C64 specific system utility routines: ----
@ -259,17 +292,13 @@ asmsub init_system() {
}}
}
asmsub reset_system() {
; Soft-reset the system back to Basic prompt.
asmsub init_system_phase2() {
%asm {{
sei
lda #14
sta $01 ; bank the kernal in
jmp (c64.RESET_VEC)
rts ; no phase 2 steps on the C64
}}
}
asmsub disable_runstop_and_charsetswitch() {
asmsub disable_runstop_and_charsetswitch() clobbers(A) {
%asm {{
lda #$80
sta 657 ; disable charset switching
@ -279,27 +308,13 @@ asmsub disable_runstop_and_charsetswitch() {
}}
}
asmsub set_irqvec_excl() clobbers(A) {
%asm {{
sei
lda #<_irq_handler
sta c64.CINV
lda #>_irq_handler
sta c64.CINV+1
cli
rts
_irq_handler jsr set_irqvec._irq_handler_init
jsr irq.irq
jsr set_irqvec._irq_handler_end
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
jmp c64.IRQDFEND ; end irq processing - don't call kernel
}}
}
asmsub set_irqvec() clobbers(A) {
asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda #0
adc #0
sta _use_kernal
sei
lda #<_irq_handler
sta c64.CINV
@ -308,9 +323,23 @@ asmsub set_irqvec() clobbers(A) {
cli
rts
_irq_handler jsr _irq_handler_init
jsr irq.irq
_modified jsr $ffff ; modified
jsr _irq_handler_end
jmp c64.IRQDFRT ; continue with normal kernel irq routine
lda _use_kernal
bne +
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
; end irq processing - don't use kernal's irq handling
pla
tay
pla
tax
pla
rti
+ jmp c64.IRQDFRT ; continue with normal kernal irq routine
_use_kernal .byte 0
_irq_handler_init
; save all zp scratch registers and the X register as these might be clobbered by the irq routine
@ -363,7 +392,7 @@ IRQ_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub restore_irqvec() {
asmsub restore_irq() clobbers(A) {
%asm {{
sei
lda #<c64.IRQDFRT
@ -379,8 +408,15 @@ asmsub restore_irqvec() {
}}
}
asmsub set_rasterirq(uword rasterpos @ AY) clobbers(A) {
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0, ubyte useKernal @Pc) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda #0
adc #0
sta set_irq._use_kernal
lda cx16.r0
ldy cx16.r0+1
sei
jsr _setup_raster_irq
lda #<_raster_irq_handler
@ -391,12 +427,21 @@ asmsub set_rasterirq(uword rasterpos @ AY) clobbers(A) {
rts
_raster_irq_handler
jsr set_irqvec._irq_handler_init
jsr irq.irq
jsr set_irqvec._irq_handler_end
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
jmp c64.IRQDFRT
jsr set_irq._irq_handler_init
_modified jsr $ffff ; modified
jsr set_irq._irq_handler_end
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda set_irq._use_kernal
bne +
; end irq processing - don't use kernal's irq handling
pla
tay
pla
tax
pla
rti
+ jmp c64.IRQDFRT ; continue with kernal irq routine
_setup_raster_irq
pha
@ -420,29 +465,271 @@ _setup_raster_irq
}}
}
asmsub set_rasterirq_excl(uword rasterpos @ AY) clobbers(A) {
%asm {{
sei
jsr set_rasterirq._setup_raster_irq
lda #<_raster_irq_handler
sta c64.CINV
lda #>_raster_irq_handler
sta c64.CINV+1
cli
rts
_raster_irq_handler
jsr set_irqvec._irq_handler_init
jsr irq.irq
jsr set_irqvec._irq_handler_end
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
jmp c64.IRQDFEND ; end irq processing - don't call kernel
}}
}
; ---- end of C64 specific system utility routines ----
}
sys {
; ------- lowlevel system routines --------
const ubyte target = 64 ; compilation target specifier. 64 = C64, 16 = CommanderX16.
asmsub reset_system() {
; Soft-reset the system back to initial power-on Basic prompt.
%asm {{
sei
lda #14
sta $01 ; bank the kernal in
jmp (c64.RESET_VEC)
}}
}
sub wait(uword jiffies) {
; --- wait approximately the given number of jiffies (1/60th seconds)
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
repeat jiffies {
ubyte jiff = lsb(c64.RDTIM16())
while jiff==lsb(c64.RDTIM16()) {
; wait until 1 jiffy has passed
}
}
}
asmsub waitvsync() clobbers(A) {
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
; note: a more accurate way to wait for vsync is to set up a vsync irq handler instead.
%asm {{
- bit c64.SCROLY
bpl -
- bit c64.SCROLY
bmi -
rts
}}
}
inline asmsub waitrastborder() {
; --- busy wait till the raster position has reached the bottom screen border (approximately)
; note: a more accurate way to do this is by using a raster irq handler instead.
%asm {{
- bit c64.SCROLY
bpl -
}}
}
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
%asm {{
ldx cx16.r0
stx P8ZP_SCRATCH_W1 ; source in ZP
ldx cx16.r0+1
stx P8ZP_SCRATCH_W1+1
ldx cx16.r1
stx P8ZP_SCRATCH_W2 ; target in ZP
ldx cx16.r1+1
stx P8ZP_SCRATCH_W2+1
cpy #0
bne _longcopy
; copy <= 255 bytes
tay
bne _copyshort
rts ; nothing to copy
_copyshort
; decrease source and target pointers so we can simply index by Y
lda P8ZP_SCRATCH_W1
bne +
dec P8ZP_SCRATCH_W1+1
+ dec P8ZP_SCRATCH_W1
lda P8ZP_SCRATCH_W2
bne +
dec P8ZP_SCRATCH_W2+1
+ dec P8ZP_SCRATCH_W2
- lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
dey
bne -
rts
_longcopy
sta P8ZP_SCRATCH_B1 ; lsb(count) = remainder in last page
tya
tax ; x = num pages (1+)
ldy #0
- lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
bne -
inc P8ZP_SCRATCH_W1+1
inc P8ZP_SCRATCH_W2+1
dex
bne -
ldy P8ZP_SCRATCH_B1
bne _copyshort
rts
}}
}
asmsub memset(uword mem @R0, uword numbytes @R1, ubyte value @A) clobbers(A,X,Y) {
%asm {{
ldy cx16.r0
sty P8ZP_SCRATCH_W1
ldy cx16.r0+1
sty P8ZP_SCRATCH_W1+1
ldx cx16.r1
ldy cx16.r1+1
jmp prog8_lib.memset
}}
}
asmsub memsetw(uword mem @R0, uword numwords @R1, uword value @AY) clobbers(A,X,Y) {
%asm {{
ldx cx16.r0
stx P8ZP_SCRATCH_W1
ldx cx16.r0+1
stx P8ZP_SCRATCH_W1+1
ldx cx16.r1
stx P8ZP_SCRATCH_W2
ldx cx16.r1+1
stx P8ZP_SCRATCH_W2+1
jmp prog8_lib.memsetw
}}
}
inline asmsub rsave() {
; save cpu status flag and all registers A, X, Y.
; see http://6502.org/tutorials/register_preservation.html
%asm {{
php
sta P8ZP_SCRATCH_REG
pha
txa
pha
tya
pha
lda P8ZP_SCRATCH_REG
}}
}
inline asmsub rrestore() {
; restore all registers and cpu status flag
%asm {{
pla
tay
pla
tax
pla
plp
}}
}
inline asmsub read_flags() -> ubyte @A {
%asm {{
php
pla
}}
}
inline asmsub clear_carry() {
%asm {{
clc
}}
}
inline asmsub set_carry() {
%asm {{
sec
}}
}
inline asmsub clear_irqd() {
%asm {{
cli
}}
}
inline asmsub set_irqd() {
%asm {{
sei
}}
}
inline asmsub exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
%asm {{
jsr c64.CLRCHN ; reset i/o channels
ldx prog8_lib.orig_stackpointer
txs
rts ; return to original caller
}}
}
inline asmsub progend() -> uword @AY {
%asm {{
lda #<prog8_program_end
ldy #>prog8_program_end
}}
}
}
cx16 {
; the sixteen virtual 16-bit registers that the CX16 has defined in the zeropage
; they are simulated on the C64 as well but their location in memory is different
; (because there's no room for them in the zeropage)
; they are allocated at the bottom of the eval-stack (should be ample space unless
; you're doing insane nesting of expressions...)
&uword r0 = $cf00
&uword r1 = $cf02
&uword r2 = $cf04
&uword r3 = $cf06
&uword r4 = $cf08
&uword r5 = $cf0a
&uword r6 = $cf0c
&uword r7 = $cf0e
&uword r8 = $cf10
&uword r9 = $cf12
&uword r10 = $cf14
&uword r11 = $cf16
&uword r12 = $cf18
&uword r13 = $cf1a
&uword r14 = $cf1c
&uword r15 = $cf1e
&ubyte r0L = $cf00
&ubyte r1L = $cf02
&ubyte r2L = $cf04
&ubyte r3L = $cf06
&ubyte r4L = $cf08
&ubyte r5L = $cf0a
&ubyte r6L = $cf0c
&ubyte r7L = $cf0e
&ubyte r8L = $cf10
&ubyte r9L = $cf12
&ubyte r10L = $cf14
&ubyte r11L = $cf16
&ubyte r12L = $cf18
&ubyte r13L = $cf1a
&ubyte r14L = $cf1c
&ubyte r15L = $cf1e
&ubyte r0H = $cf01
&ubyte r1H = $cf03
&ubyte r2H = $cf05
&ubyte r3H = $cf07
&ubyte r4H = $cf09
&ubyte r5H = $cf0b
&ubyte r6H = $cf0d
&ubyte r7H = $cf0f
&ubyte r8H = $cf11
&ubyte r9H = $cf13
&ubyte r10H = $cf15
&ubyte r11H = $cf17
&ubyte r12H = $cf19
&ubyte r13H = $cf1b
&ubyte r14H = $cf1d
&ubyte r15H = $cf1f
}

View File

@ -4,7 +4,6 @@
;
; indent format: TABS, size=8
%target c64
%import syslib
%import conv
@ -16,7 +15,30 @@ const ubyte DEFAULT_HEIGHT = 25
sub clear_screen() {
clear_screenchars(' ')
txt.chrout(147)
}
sub home() {
txt.chrout(19)
}
sub nl() {
txt.chrout('\n')
}
sub spc() {
txt.chrout(' ')
}
asmsub column(ubyte col @A) clobbers(A, X, Y) {
; ---- set the cursor on the given column (starting with 0) on the current line
%asm {{
sec
jsr c64.PLOT
tay
clc
jmp c64.PLOT
}}
}
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
@ -38,13 +60,13 @@ asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
; ---- clear the character screen with the given fill character (leaves colors)
; (assumes screen matrix is at the default address)
%asm {{
ldy #0
_loop sta c64.Screen,y
sta c64.Screen+$0100,y
sta c64.Screen+$0200,y
sta c64.Screen+$02e8,y
iny
bne _loop
ldy #250
- sta c64.Screen+250*0-1,y
sta c64.Screen+250*1-1,y
sta c64.Screen+250*2-1,y
sta c64.Screen+250*3-1,y
dey
bne -
rts
}}
}
@ -53,13 +75,13 @@ asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
; ---- clear the character screen colors with the given color (leaves characters).
; (assumes color matrix is at the default address)
%asm {{
ldy #0
_loop sta c64.Colors,y
sta c64.Colors+$0100,y
sta c64.Colors+$0200,y
sta c64.Colors+$02e8,y
iny
bne _loop
ldy #250
- sta c64.Colors+250*0-1,y
sta c64.Colors+250*1-1,y
sta c64.Colors+250*2-1,y
sta c64.Colors+250*3-1,y
dey
bne -
rts
}}
}
@ -83,29 +105,31 @@ asmsub scroll_left (ubyte alsocolors @ Pc) clobbers(A, Y) {
%asm {{
stx P8ZP_SCRATCH_REG
bcs +
jmp _scroll_screen
bcc _scroll_screen
+ ; scroll the color memory
+ ; scroll the screen and the color memory
ldx #0
ldy #38
-
.for row=0, row<=24, row+=1
lda c64.Colors + 40*row + 1,x
sta c64.Colors + 40*row,x
.next
.for row=0, row<=24, row+=1
lda c64.Screen + 40*row + 1,x
sta c64.Screen + 40*row + 0,x
lda c64.Colors + 40*row + 1,x
sta c64.Colors + 40*row + 0,x
.next
inx
dey
bpl -
rts
_scroll_screen ; scroll the screen memory
_scroll_screen ; scroll only the screen memory
ldx #0
ldy #38
-
.for row=0, row<=24, row+=1
lda c64.Screen + 40*row + 1,x
sta c64.Screen + 40*row,x
.next
.for row=0, row<=24, row+=1
lda c64.Screen + 40*row + 1,x
sta c64.Screen + 40*row + 0,x
.next
inx
dey
bpl -
@ -121,26 +145,28 @@ asmsub scroll_right (ubyte alsocolors @ Pc) clobbers(A) {
; Carry flag determines if screen color data must be scrolled too
%asm {{
stx P8ZP_SCRATCH_REG
bcs +
jmp _scroll_screen
bcc _scroll_screen
+ ; scroll the color memory
+ ; scroll the screen and the color memory
ldx #38
-
.for row=0, row<=24, row+=1
lda c64.Colors + 40*row + 0,x
sta c64.Colors + 40*row + 1,x
.next
.for row=0, row<=24, row+=1
lda c64.Screen + 40*row + 0,x
sta c64.Screen + 40*row + 1,x
lda c64.Colors + 40*row + 0,x
sta c64.Colors + 40*row + 1,x
.next
dex
bpl -
rts
_scroll_screen ; scroll the screen memory
_scroll_screen ; scroll only the screen memory
ldx #38
-
.for row=0, row<=24, row+=1
lda c64.Screen + 40*row + 0,x
sta c64.Screen + 40*row + 1,x
.next
.for row=0, row<=24, row+=1
lda c64.Screen + 40*row + 0,x
sta c64.Screen + 40*row + 1,x
.next
dex
bpl -
@ -155,26 +181,28 @@ asmsub scroll_up (ubyte alsocolors @ Pc) clobbers(A) {
; Carry flag determines if screen color data must be scrolled too
%asm {{
stx P8ZP_SCRATCH_REG
bcs +
jmp _scroll_screen
bcc _scroll_screen
+ ; scroll the color memory
+ ; scroll the screen and the color memory
ldx #39
-
.for row=1, row<=24, row+=1
lda c64.Colors + 40*row,x
sta c64.Colors + 40*(row-1),x
.next
.for row=1, row<=24, row+=1
lda c64.Screen + 40*row,x
sta c64.Screen + 40*(row-1),x
lda c64.Colors + 40*row,x
sta c64.Colors + 40*(row-1),x
.next
dex
bpl -
rts
_scroll_screen ; scroll the screen memory
_scroll_screen ; scroll only the screen memory
ldx #39
-
.for row=1, row<=24, row+=1
lda c64.Screen + 40*row,x
sta c64.Screen + 40*(row-1),x
.next
.for row=1, row<=24, row+=1
lda c64.Screen + 40*row,x
sta c64.Screen + 40*(row-1),x
.next
dex
bpl -
@ -189,26 +217,28 @@ asmsub scroll_down (ubyte alsocolors @ Pc) clobbers(A) {
; Carry flag determines if screen color data must be scrolled too
%asm {{
stx P8ZP_SCRATCH_REG
bcs +
jmp _scroll_screen
bcc _scroll_screen
+ ; scroll the color memory
+ ; scroll the screen and the color memory
ldx #39
-
.for row=23, row>=0, row-=1
lda c64.Colors + 40*row,x
sta c64.Colors + 40*(row+1),x
.next
.for row=23, row>=0, row-=1
lda c64.Colors + 40*row,x
sta c64.Colors + 40*(row+1),x
lda c64.Screen + 40*row,x
sta c64.Screen + 40*(row+1),x
.next
dex
bpl -
rts
_scroll_screen ; scroll the screen memory
_scroll_screen ; scroll only the screen memory
ldx #39
-
.for row=23, row>=0, row-=1
lda c64.Screen + 40*row,x
sta c64.Screen + 40*(row+1),x
.next
.for row=23, row>=0, row-=1
lda c64.Screen + 40*row,x
sta c64.Screen + 40*(row+1),x
.next
dex
bpl -
@ -555,7 +585,7 @@ _colormod sta $ffff ; modified
}
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
; ---- safe wrapper around PLOT kernel routine, to save the X register.
; ---- safe wrapper around PLOT kernal routine, to save the X register.
%asm {{
stx P8ZP_SCRATCH_REG
tax

View File

@ -1,30 +1,518 @@
; Prog8 definitions for number conversions routines.
; Number conversions routines.
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
;
; indent format: TABS, size=8
conv {
; ----- number conversions to decimal strings
; ----- number conversions to decimal strings ----
asmsub ubyte2decimal (ubyte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
; ---- A to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
%asm {{
ldy #uword2decimal.ASCII_0_OFFSET
bne uword2decimal.hex_try200
str string_out = "????????????????" ; result buffer for the string conversion routines
asmsub str_ub0 (ubyte value @ A) clobbers(A,Y) {
; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total)
%asm {{
phx
jsr conv.ubyte2decimal
sty string_out
sta string_out+1
stx string_out+2
lda #0
sta string_out+3
plx
rts
}}
}
asmsub str_ub (ubyte value @ A) clobbers(A,Y) {
; ---- convert the ubyte in A in decimal string form, without left padding 0s
%asm {{
phx
ldy #0
sty P8ZP_SCRATCH_B1
jsr conv.ubyte2decimal
_output_byte_digits
; hundreds?
cpy #'0'
beq +
pha
tya
ldy P8ZP_SCRATCH_B1
sta string_out,y
pla
inc P8ZP_SCRATCH_B1
; tens?
+ ldy P8ZP_SCRATCH_B1
cmp #'0'
beq +
sta string_out,y
iny
+ ; ones.
txa
sta string_out,y
iny
lda #0
sta string_out,y
plx
rts
}}
}
asmsub str_b (byte value @ A) clobbers(A,Y) {
; ---- convert the byte in A in decimal string form, without left padding 0s
%asm {{
phx
ldy #0
sty P8ZP_SCRATCH_B1
cmp #0
bpl +
pha
lda #'-'
sta string_out
inc P8ZP_SCRATCH_B1
pla
+ jsr conv.byte2decimal
bra str_ub._output_byte_digits
}}
}
asmsub str_ubhex (ubyte value @ A) clobbers(A,Y) {
; ---- convert the ubyte in A in hex string form
%asm {{
jsr conv.ubyte2hex
sta string_out
sty string_out+1
lda #0
sta string_out+2
rts
}}
}
asmsub str_ubbin (ubyte value @ A) clobbers(A,Y) {
; ---- convert the ubyte in A in binary string form
%asm {{
sta P8ZP_SCRATCH_B1
ldy #0
sty string_out+8
ldy #7
- lsr P8ZP_SCRATCH_B1
bcc +
lda #'1'
bne _digit
+ lda #'0'
_digit sta string_out,y
dey
bpl -
rts
}}
}
asmsub str_uwbin (uword value @ AY) clobbers(A,Y) {
; ---- convert the uword in A/Y in binary string form
%asm {{
sta P8ZP_SCRATCH_REG
tya
jsr str_ubbin
ldy #0
sty string_out+16
ldy #7
- lsr P8ZP_SCRATCH_REG
bcc +
lda #'1'
bne _digit
+ lda #'0'
_digit sta string_out+8,y
dey
bpl -
rts
}}
}
asmsub str_uwhex (uword value @ AY) clobbers(A,Y) {
; ---- convert the uword in A/Y in hexadecimal string form (4 digits)
%asm {{
pha
tya
jsr conv.ubyte2hex
sta string_out
sty string_out+1
pla
jsr conv.ubyte2hex
sta string_out+2
sty string_out+3
lda #0
sta string_out+4
rts
}}
}
asmsub str_uw0 (uword value @ AY) clobbers(A,Y) {
; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total)
%asm {{
phx
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
sta string_out,y
beq +
iny
bne -
+ plx
rts
}}
}
asmsub str_uw (uword value @ AY) clobbers(A,Y) {
; ---- convert the uword in A/Y in decimal string form, without left padding 0s
%asm {{
phx
jsr conv.uword2decimal
ldx #0
_output_digits
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq _allzero
cmp #'0'
bne _gotdigit
iny
bne -
_gotdigit sta string_out,x
inx
iny
lda conv.uword2decimal.decTenThousands,y
bne _gotdigit
_end lda #0
sta string_out,x
plx
rts
_allzero lda #'0'
sta string_out,x
inx
bne _end
}}
}
asmsub str_w (word value @ AY) clobbers(A,Y) {
; ---- convert the (signed) word in A/Y in decimal string form, without left padding 0's
%asm {{
cpy #0
bpl str_uw
phx
pha
lda #'-'
sta string_out
tya
eor #255
tay
pla
eor #255
clc
adc #1
bcc +
iny
+ jsr conv.uword2decimal
ldx #1
bne str_uw._output_digits
}}
}
; ---- string conversion to numbers -----
asmsub any2uword(str string @AY) clobbers(Y) -> ubyte @A {
; -- parses a string into a 16 bit unsigned number. String may be in decimal, hex or binary format.
; (the latter two require a $ or % prefix to be recognised)
; (any non-digit character will terminate the number string that is parsed)
; returns amount of processed characters in A, and the parsed number will be in cx16.r15.
; if the string was invalid, 0 will be returned in A.
%asm {{
pha
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
lda (P8ZP_SCRATCH_W1),y
ldy P8ZP_SCRATCH_W1+1
cmp #'$'
beq _hex
cmp #'%'
beq _bin
pla
jsr str2uword
jmp _result
_hex pla
jsr hex2uword
jmp _result
_bin pla
jsr bin2uword
_result
pha
lda cx16.r15
sta P8ZP_SCRATCH_B1 ; result value
pla
sta cx16.r15
sty cx16.r15+1
lda P8ZP_SCRATCH_B1
rts
}}
}
asmsub uword2decimal (uword value @ AY) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- convert 16 bit uword in A/Y to decimal
; output in uword2decimal.decTenThousands, decThousands, decHundreds, decTens, decOnes
; (these are terminated by a zero byte so they can be easily printed)
; also returns Y = 100's, A = 10's, X = 1's
inline asmsub str2ubyte(str string @AY) clobbers(Y) -> ubyte @A {
; -- returns in A the unsigned byte value of the string number argument in AY
; the number may NOT be preceded by a + sign and may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
; result in A, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
%asm {{
jsr conv.str2uword
}}
}
%asm {{
inline asmsub str2byte(str string @AY) clobbers(Y) -> ubyte @A {
; -- returns in A the signed byte value of the string number argument in AY
; the number may be preceded by a + or - sign but may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
; result in A, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
%asm {{
jsr conv.str2word
}}
}
asmsub str2uword(str string @AY) -> uword @AY {
; -- returns the unsigned word value of the string number argument in AY
; the number may NOT be preceded by a + sign and may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
; result in AY, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
%asm {{
_result = P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sty _result
sty _result+1
sty cx16.r15+1
_loop
lda (P8ZP_SCRATCH_W2),y
sec
sbc #48
bpl _digit
_done
sty cx16.r15
lda _result
ldy _result+1
rts
_digit
cmp #10
bcs _done
; add digit to result
pha
jsr _result_times_10
pla
clc
adc _result
sta _result
bcc +
inc _result+1
+ iny
bne _loop
; never reached
_result_times_10 ; (W*4 + W)*2
lda _result+1
sta P8ZP_SCRATCH_REG
lda _result
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
clc
adc _result
sta _result
lda P8ZP_SCRATCH_REG
adc _result+1
asl _result
rol a
sta _result+1
rts
}}
}
asmsub str2word(str string @AY) -> word @AY {
; -- returns the signed word value of the string number argument in AY
; the number may be preceded by a + or - sign but may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
; result in AY, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
%asm {{
_result = P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sty _result
sty _result+1
sty _negative
sty cx16.r15+1
lda (P8ZP_SCRATCH_W2),y
cmp #'+'
bne +
iny
+ cmp #'-'
bne _parse
inc _negative
iny
_parse lda (P8ZP_SCRATCH_W2),y
sec
sbc #48
bpl _digit
_done
sty cx16.r15
lda _negative
beq +
sec
lda #0
sbc _result
sta _result
lda #0
sbc _result+1
sta _result+1
+ lda _result
ldy _result+1
rts
_digit
cmp #10
bcs _done
; add digit to result
pha
jsr str2uword._result_times_10
pla
clc
adc _result
sta _result
bcc +
inc _result+1
+ iny
bne _parse
; never reached
_negative .byte 0
}}
}
asmsub hex2uword(str string @AY) -> uword @AY {
; -- hexadecimal string (with or without '$') to uword.
; string may be in petscii or c64-screencode encoding.
; stops parsing at the first character that's not a hex digit (except leading $)
; result in AY, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
%asm {{
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sty P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
sty cx16.r15+1
lda (P8ZP_SCRATCH_W2),y
beq _stop
cmp #'$'
bne _loop
iny
_loop
lda #0
sta P8ZP_SCRATCH_B1
lda (P8ZP_SCRATCH_W2),y
beq _stop
cmp #7 ; screencode letters A-F are 1-6
bcc _add_letter
cmp #'g'
bcs _stop
cmp #'a'
bcs _add_letter
cmp #'0'
bcc _stop
cmp #'9'+1
bcs _stop
_calc
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
and #$0f
clc
adc P8ZP_SCRATCH_B1
ora P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W1
iny
bne _loop
_stop
sty cx16.r15
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
rts
_add_letter
pha
lda #9
sta P8ZP_SCRATCH_B1
pla
jmp _calc
}}
}
asmsub bin2uword(str string @AY) -> uword @AY {
; -- binary string (with or without '%') to uword.
; stops parsing at the first character that's not a 0 or 1. (except leading %)
; result in AY, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
%asm {{
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sty P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
sty cx16.r15+1
lda (P8ZP_SCRATCH_W2),y
beq _stop
cmp #'%'
bne _loop
iny
_loop
lda (P8ZP_SCRATCH_W2),y
cmp #'0'
bcc _stop
cmp #'2'
bcs _stop
_first asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
and #1
ora P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W1
iny
bne _loop
_stop
sty cx16.r15
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
rts
}}
}
; ----- low level number conversions to decimal strings ----
asmsub ubyte2decimal (ubyte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- A to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
%asm {{
ldy #uword2decimal.ASCII_0_OFFSET
bne uword2decimal.hex_try200
rts
}}
}
asmsub uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- convert 16 bit uword in A/Y to decimal
; output in uword2decimal.decTenThousands, decThousands, decHundreds, decTens, decOnes
; (these are terminated by a zero byte so they can be easily printed)
; also returns Y = 100's, A = 10's, X = 1's
%asm {{
;Convert 16 bit Hex to Decimal (0-65535) Rev 2
;By Omegamatrix Further optimizations by tepples
@ -193,11 +681,7 @@ decOnes .byte 0
}}
}
; ----- utility functions ----
asmsub byte2decimal (byte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
asmsub byte2decimal (byte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- A (signed byte) to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
; note: if the number is negative, you have to deal with the '-' yourself!
%asm {{
@ -210,7 +694,7 @@ asmsub byte2decimal (byte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
}}
}
asmsub ubyte2hex (ubyte value @ A) -> ubyte @ A, ubyte @ Y {
asmsub ubyte2hex (ubyte value @A) -> ubyte @A, ubyte @Y {
; ---- A to hex petscii string in AY (first hex char in A, second hex char in Y)
%asm {{
stx P8ZP_SCRATCH_REG
@ -232,7 +716,7 @@ _hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as
}}
}
asmsub uword2hex (uword value @ AY) clobbers(A,Y) {
asmsub uword2hex (uword value @AY) clobbers(A,Y) {
; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated)
%asm {{
sta P8ZP_SCRATCH_REG
@ -245,209 +729,8 @@ asmsub uword2hex (uword value @ AY) clobbers(A,Y) {
sta output+2
sty output+3
rts
output .text "0000", $00 ; 0-terminated output buffer (to make printing easier)
output .text "0000", $00 ; 0-terminated output buffer (to make printing easier)
}}
}
asmsub str2ubyte(str string @ AY) clobbers(Y) -> ubyte @A {
; -- returns the unsigned byte value of the string number argument in AY
; the number may NOT be preceded by a + sign and may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
%asm {{
jmp str2uword
}}
}
asmsub str2byte(str string @ AY) clobbers(Y) -> ubyte @A {
; -- returns the signed byte value of the string number argument in AY
; the number may be preceded by a + or - sign but may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
%asm {{
jmp str2word
}}
}
asmsub str2uword(str string @ AY) -> uword @ AY {
; -- returns the unsigned word value of the string number argument in AY
; the number may NOT be preceded by a + sign and may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
%asm {{
_result = P8ZP_SCRATCH_W2
sta _mod+1
sty _mod+2
ldy #0
sty _result
sty _result+1
_mod lda $ffff,y ; modified
sec
sbc #48
bpl +
_done ; return result
lda _result
ldy _result+1
rts
+ cmp #10
bcs _done
; add digit to result
pha
jsr _result_times_10
pla
clc
adc _result
sta _result
bcc +
inc _result+1
+ iny
bne _mod
; never reached
_result_times_10 ; (W*4 + W)*2
lda _result+1
sta P8ZP_SCRATCH_REG
lda _result
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
clc
adc _result
sta _result
lda P8ZP_SCRATCH_REG
adc _result+1
asl _result
rol a
sta _result+1
rts
}}
}
asmsub str2word(str string @ AY) -> word @ AY {
; -- returns the signed word value of the string number argument in AY
; the number may be preceded by a + or - sign but may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
%asm {{
_result = P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
sty _result
sty _result+1
sty _negative
lda (P8ZP_SCRATCH_W1),y
cmp #'+'
bne +
iny
+ cmp #'-'
bne _parse
inc _negative
iny
_parse lda (P8ZP_SCRATCH_W1),y
sec
sbc #48
bpl _digit
_done ; return result
lda _negative
beq +
sec
lda #0
sbc _result
sta _result
lda #0
sbc _result+1
sta _result+1
+ lda _result
ldy _result+1
rts
_digit cmp #10
bcs _done
; add digit to result
pha
jsr str2uword._result_times_10
pla
clc
adc _result
sta _result
bcc +
inc _result+1
+ iny
bne _parse
; never reached
_negative .byte 0
}}
}
asmsub hex2uword(str string @ AY) -> uword @AY {
; -- hexadecimal string with or without '$' to uword.
; string may be in petscii or c64-screencode encoding.
%asm {{
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sty P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop ldy #0
sty P8ZP_SCRATCH_B1
lda (P8ZP_SCRATCH_W2),y
beq _stop
cmp #'$'
beq _skip
cmp #7
bcc _add_nine
cmp #'9'
beq _calc
bcs _add_nine
_calc asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
and #$0f
clc
adc P8ZP_SCRATCH_B1
ora P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W1
_skip inc P8ZP_SCRATCH_W2
bne _loop
inc P8ZP_SCRATCH_W2+1
bne _loop
_stop lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
rts
_add_nine ldy #9
sty P8ZP_SCRATCH_B1
bne _calc
}}
}
asmsub bin2uword(str string @ AY) -> uword @AY {
; -- binary string with or without '%' to uword.
%asm {{
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sty P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop lda (P8ZP_SCRATCH_W2),y
beq _stop
cmp #'%'
beq +
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
and #1
ora P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W1
+ inc P8ZP_SCRATCH_W2
bne _loop
inc P8ZP_SCRATCH_W2+1
bne _loop
_stop lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
rts
}}
}
}

View File

@ -4,11 +4,12 @@
;
; indent format: TABS, size=8
%target cx16
%option enable_floats
floats {
; ---- this block contains C-64 floating point related functions ----
; ---- this block contains C-64 compatible floating point related functions ----
; the addresses are from cx16 V39 emulator and roms! they won't work on older versions.
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
@ -43,45 +44,44 @@ romsub $fe1e = NORMAL() clobbers(A,X,Y) ; normalize fac1 (?)
romsub $fe24 = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
romsub $fe27 = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
romsub $fe2a = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
romsub $fe33 = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
romsub $fe36 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
romsub $fe3c = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
romsub $fe3f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
romsub $fe42 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
romsub $fe30 = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2
romsub $fe33 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
romsub $fe36 = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
romsub $fe39 = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
romsub $fe3c = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
romsub $fe48 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
romsub $fe4b = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
romsub $fe4e = MOVFA() clobbers(A,X) ; copy fac2 to fac1
romsub $fe51 = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
romsub $fe54 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
romsub $fe5a = SIGN() -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
romsub $fe5d = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
romsub $fe60 = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
romsub $fe6c = ABS() ; fac1 = ABS(fac1)
romsub $fe6f = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
romsub $fe78 = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
romsub $fe7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
romsub $fe81 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
romsub $fe8a = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
romsub $fe8d = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
romsub $fe93 = NEGOP() clobbers(A) ; switch the sign of fac1
romsub $fe96 = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
romsub $fe9f = RND2(byte value @A) clobbers(A,X,Y) ; fac1 = RND(A) float random number generator
romsub $fea2 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
romsub $fea5 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
romsub $fea8 = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
romsub $feab = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
romsub $feae = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
romsub $fe42 = MOVFM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1
romsub $fe45 = MOVMF(uword mflpt @ XY) clobbers(A,X,Y) ; store fac1 to memory X/Y as 5-byte mflpt
romsub $fe48 = MOVFA() clobbers(A,X) ; copy fac2 to fac1
romsub $fe4b = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
romsub $fe4e = MOVEF() clobbers(A,X) ; copy fac1 to fac2
romsub $fe54 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
romsub $fe57 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
romsub $fe5a = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
romsub $fe66 = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
romsub $fe69 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
romsub $fe72 = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
romsub $fe78 = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
romsub $fe7b = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
romsub $fe81 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
romsub $fe84 = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
romsub $fe8a = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
romsub $fe8d = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
romsub $fe96 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
romsub $fe99 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
romsub $fe9c = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
romsub $fe9f = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
romsub $fea2 = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
%asm {{
phx
sta P8ZP_SCRATCH_W2
sta _tmp
sty P8ZP_SCRATCH_B1
tya
ldy P8ZP_SCRATCH_W2
ldy _tmp
jsr GIVAYF ; load it as signed... correct afterwards
lda P8ZP_SCRATCH_B1
bpl +
@ -90,6 +90,7 @@ asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
jsr FADD
+ plx
rts
_tmp .byte 0
_flt65536 .byte 145,0,0,0,0 ; 65536.0
}}
}
@ -98,9 +99,9 @@ _flt65536 .byte 145,0,0,0,0 ; 65536.0
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
%asm {{
sta P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_B1
tya
ldy P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_B1
jmp GIVAYF ; this uses the inverse order, Y/A
}}
}
@ -109,9 +110,9 @@ asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
; ---- fac1 to signed word in A/Y
%asm {{
jsr FTOSWORDYA ; note the inverse Y/A order
sta P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_B1
tya
ldy P8ZP_SCRATCH_REG
ldy P8ZP_SCRATCH_B1
rts
}}
}
@ -127,6 +128,14 @@ asmsub GETADRAY () clobbers(X) -> uword @ AY {
}}
}
asmsub FREADUY (ubyte value @Y) {
; -- 8 bit unsigned Y -> float in fac1
%asm {{
lda #0
jmp GIVAYF
}}
}
sub print_f (float value) {
; ---- prints the floating point value (without a newline).
%asm {{
@ -148,7 +157,7 @@ sub print_f (float value) {
}}
}
%asminclude "library:c64/floats.asm", ""
%asminclude "library:c64/floats_funcs.asm", ""
%asminclude "library:c64/floats.asm"
%asminclude "library:c64/floats_funcs.asm"
}

View File

@ -0,0 +1,989 @@
; Bitmap pixel graphics routines for the CommanderX16
; Custom routines to use the full-screen 640x480 and 320x240 screen modes.
; (These modes are not supported by the documented GRAPH_xxxx kernal routines)
;
; No text layer is currently shown, text can be drawn as part of the bitmap itself.
; Note: for similar graphics routines that also work on the C-64, use the "graphics" module instead.
; Note: for color palette manipulation, use the "palette" module or write Vera registers yourself.
; Note: this library implements code for various resolutions and color depths. This takes up memory.
; If you're memory constrained you should probably not use this built-in library,
; but make a copy in your project only containing the code for the required resolution.
;
;
; SCREEN MODE LIST:
; mode 0 = reset back to default text mode
; mode 1 = bitmap 320 x 240 monochrome
; mode 2 = bitmap 320 x 240 x 4c (TODO not yet implemented)
; mode 3 = bitmap 320 x 240 x 16c (TODO not yet implemented)
; mode 4 = bitmap 320 x 240 x 256c
; mode 5 = bitmap 640 x 480 monochrome
; mode 6 = bitmap 640 x 480 x 4c
; higher color dephts in highres are not supported due to lack of VRAM
; TODO can we make a FB vector table and emulation routines for the Cx16s' GRAPH_init() call? to replace the builtin 320x200 fb driver?
gfx2 {
; read-only control variables:
ubyte active_mode = 0
uword width = 0
uword height = 0
ubyte bpp = 0
ubyte monochrome_dont_stipple_flag = false ; set to false to enable stippling mode in monochrome displaymodes
sub screen_mode(ubyte mode) {
when mode {
1 -> {
; lores monochrome
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 64
cx16.VERA_DC_VSCALE = 64
cx16.VERA_L1_CONFIG = %00000100
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = 0
width = 320
height = 240
bpp = 1
}
; TODO modes 2, 3 not yet implemented
4 -> {
; lores 256c
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 64
cx16.VERA_DC_VSCALE = 64
cx16.VERA_L1_CONFIG = %00000111
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = 0
width = 320
height = 240
bpp = 8
}
5 -> {
; highres monochrome
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 128
cx16.VERA_DC_VSCALE = 128
cx16.VERA_L1_CONFIG = %00000100
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = %00000001
width = 640
height = 480
bpp = 1
}
6 -> {
; highres 4c
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 128
cx16.VERA_DC_VSCALE = 128
cx16.VERA_L1_CONFIG = %00000101
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = %00000001
width = 640
height = 480
bpp = 2
}
else -> {
; back to default text mode and colors
cx16.VERA_CTRL = %10000000 ; reset VERA and palette
c64.CINT() ; back to text mode
width = 0
height = 0
bpp = 0
mode = 0
}
}
active_mode = mode
if bpp
clear_screen()
}
sub clear_screen() {
monochrome_stipple(false)
position(0, 0)
when active_mode {
1 -> {
; lores monochrome
repeat 240/2/8
cs_innerloop640()
}
; TODO mode 2, 3
4 -> {
; lores 256c
repeat 240/2
cs_innerloop640()
}
5 -> {
; highres monochrome
repeat 480/8
cs_innerloop640()
}
6 -> {
; highres 4c
repeat 480/4
cs_innerloop640()
}
; modes 7 and 8 not supported due to lack of VRAM
}
position(0, 0)
}
sub monochrome_stipple(ubyte enable) {
monochrome_dont_stipple_flag = not enable
}
sub rect(uword x, uword y, uword width, uword height, ubyte color) {
if width==0 or height==0
return
horizontal_line(x, y, width, color)
if height==1
return
horizontal_line(x, y+height-1, width, color)
vertical_line(x, y+1, height-2, color)
if width==1
return
vertical_line(x+width-1, y+1, height-2, color)
}
sub fillrect(uword x, uword y, uword width, uword height, ubyte color) {
if width==0
return
repeat height {
horizontal_line(x, y, width, color)
y++
}
}
sub horizontal_line(uword x, uword y, uword length, ubyte color) {
if length==0
return
when active_mode {
1, 5 -> {
; monochrome modes, either resolution
ubyte separate_pixels = (8-lsb(x)) & 7
if separate_pixels as uword > length
separate_pixels = lsb(length)
repeat separate_pixels {
; TODO optimize this by writing a masked byte in 1 go
plot(x, y, color)
x++
}
length -= separate_pixels
if length {
position(x, y)
separate_pixels = lsb(length) & 7
x += length & $fff8
%asm {{
lsr length+1
ror length
lsr length+1
ror length
lsr length+1
ror length
lda color
bne +
ldy #0 ; black
bra _loop
+ lda monochrome_dont_stipple_flag
beq _stipple
ldy #255 ; don't stipple
bra _loop
_stipple lda y
and #1 ; determine stipple pattern to use
bne +
ldy #%01010101
bra _loop
+ ldy #%10101010
_loop lda length
ora length+1
beq _done
sty cx16.VERA_DATA0
lda length
bne +
dec length+1
+ dec length
bra _loop
_done
}}
repeat separate_pixels {
; TODO optimize this by writing a masked byte in 1 go
plot(x, y, color)
x++
}
}
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off again
}
4 -> {
; lores 256c
position(x, y)
%asm {{
lda color
phx
ldx length+1
beq +
ldy #0
- sta cx16.VERA_DATA0
iny
bne -
dex
bne -
+ ldy length ; remaining
beq +
- sta cx16.VERA_DATA0
dey
bne -
+ plx
}}
}
6 -> {
; highres 4c
; TODO also mostly usable for lores 4c?
color &= 3
ubyte[4] colorbits
ubyte ii
for ii in 3 downto 0 {
colorbits[ii] = color
color <<= 2
}
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
%asm {{
lda cx16.VERA_ADDR_H
and #%00000111 ; no auto advance
sta cx16.VERA_ADDR_H
stz cx16.VERA_CTRL ; setup vera addr 0
lda cx16.r1
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
phx
ldx x
}}
repeat length {
%asm {{
txa
and #3
tay
lda cx16.VERA_DATA0
and gfx2.plot.mask4c,y
ora colorbits,y
sta cx16.VERA_DATA0
cpy #%00000011 ; next vera byte?
bne ++
inc cx16.VERA_ADDR_L
bne ++
inc cx16.VERA_ADDR_M
+ bne +
inc cx16.VERA_ADDR_H
+ inx ; next pixel
}}
}
%asm {{
plx
}}
}
}
}
sub vertical_line(uword x, uword y, uword height, ubyte color) {
when active_mode {
1, 5 -> {
; monochrome, lo-res
cx16.r15L = gfx2.plot.bits[x as ubyte & 7] ; bitmask
if color {
if monochrome_dont_stipple_flag {
; draw continuous line.
position2(x,y,true)
if active_mode==1
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
else
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
repeat height {
%asm {{
lda cx16.VERA_DATA0
ora cx16.r15L
sta cx16.VERA_DATA1
}}
}
} else {
; draw stippled line.
if x&1 {
y++
height--
}
position2(x,y,true)
if active_mode==1
set_both_strides(12) ; 80 increment = 2 line in 320 px monochrome
else
set_both_strides(13) ; 160 increment = 2 line in 640 px monochrome
repeat height/2 {
%asm {{
lda cx16.VERA_DATA0
ora cx16.r15L
sta cx16.VERA_DATA1
}}
}
}
} else {
position2(x,y,true)
cx16.r15 = ~cx16.r15 ; erase pixels
if active_mode==1
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
else
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
repeat height {
%asm {{
lda cx16.VERA_DATA0
and cx16.r15L
sta cx16.VERA_DATA1
}}
}
}
}
4 -> {
; lores 256c
; set vera auto-increment to 320 pixel increment (=next line)
position(x,y)
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | (14<<4)
%asm {{
ldy height
beq +
lda color
- sta cx16.VERA_DATA0
dey
bne -
+
}}
}
6 -> {
; highres 4c
; use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment is possible
if height==0
return
position2(x,y,true)
set_both_strides(13) ; 160 increment = 1 line in 640 px 4c mode
color &= 3
color <<= gfx2.plot.shift4c[lsb(x) & 3]
ubyte mask = gfx2.plot.mask4c[lsb(x) & 3]
repeat height {
%asm {{
lda cx16.VERA_DATA0
and mask
ora color
sta cx16.VERA_DATA1
}}
}
}
}
sub set_both_strides(ubyte stride) {
stride <<= 4
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
cx16.VERA_CTRL = 1
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
}
}
sub line(uword @zp x1, uword @zp y1, uword @zp x2, uword @zp y2, ubyte color) {
; Bresenham algorithm.
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
if y1>y2 {
; make sure dy is always positive to have only 4 instead of 8 special cases
swap(x1, x2)
swap(y1, y2)
}
word @zp dx = (x2 as word)-x1
word @zp dy = (y2 as word)-y1
if dx==0 {
vertical_line(x1, y1, abs(dy) as uword +1, color)
return
}
if dy==0 {
if x1>x2
x1=x2
horizontal_line(x1, y1, abs(dx) as uword +1, color)
return
}
word @zp d = 0
cx16.r13 = true ; 'positive_ix'
if dx < 0 {
dx = -dx
cx16.r13 = false
}
word @zp dx2 = dx*2
word @zp dy2 = dy*2
cx16.r14 = x1 ; internal plot X
if dx >= dy {
if cx16.r13 {
repeat {
plot(cx16.r14, y1, color)
if cx16.r14==x2
return
cx16.r14++
d += dy2
if d > dx {
y1++
d -= dx2
}
}
} else {
repeat {
plot(cx16.r14, y1, color)
if cx16.r14==x2
return
cx16.r14--
d += dy2
if d > dx {
y1++
d -= dx2
}
}
}
}
else {
if cx16.r13 {
repeat {
plot(cx16.r14, y1, color)
if y1 == y2
return
y1++
d += dx2
if d > dy {
cx16.r14++
d -= dy2
}
}
} else {
repeat {
plot(cx16.r14, y1, color)
if y1 == y2
return
y1++
d += dx2
if d > dy {
cx16.r14--
d -= dy2
}
}
}
}
}
sub circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, ubyte color) {
; Midpoint algorithm.
if radius==0
return
ubyte @zp xx = radius
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-xx
; R14 = internal plot X
; R15 = internal plot Y
while xx>=yy {
cx16.r14 = xcenter + xx
cx16.r15 = ycenter + yy
plot(cx16.r14, cx16.r15, color)
cx16.r14 = xcenter - xx
plot(cx16.r14, cx16.r15, color)
cx16.r14 = xcenter + xx
cx16.r15 = ycenter - yy
plot(cx16.r14, cx16.r15, color)
cx16.r14 = xcenter - xx
plot(cx16.r14, cx16.r15, color)
cx16.r14 = xcenter + yy
cx16.r15 = ycenter + xx
plot(cx16.r14, cx16.r15, color)
cx16.r14 = xcenter - yy
plot(cx16.r14, cx16.r15, color)
cx16.r14 = xcenter + yy
cx16.r15 = ycenter - xx
plot(cx16.r14, cx16.r15, color)
cx16.r14 = xcenter - yy
plot(cx16.r14, cx16.r15, color)
yy++
if decisionOver2<=0
decisionOver2 += (yy as word)*2+1
else {
xx--
decisionOver2 += (yy as word -xx)*2+1
}
}
}
sub disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, ubyte color) {
; Midpoint algorithm, filled
if radius==0
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color)
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color)
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color)
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color)
yy++
if decisionOver2<=0
decisionOver2 += (yy as word)*2+1
else {
radius--
decisionOver2 += (yy as word -radius)*2+1
}
}
}
sub plot(uword @zp x, uword y, ubyte color) {
ubyte[8] bits = [128, 64, 32, 16, 8, 4, 2, 1]
ubyte[4] mask4c = [%00111111, %11001111, %11110011, %11111100]
ubyte[4] shift4c = [6,4,2,0]
when active_mode {
1 -> {
; lores monochrome
%asm {{
lda x
eor y
ora monochrome_dont_stipple_flag
and #1
}}
if_nz {
cx16.r0L = lsb(x) & 7 ; xbits
x /= 8
x += y*(320/8)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda x+1
sta cx16.VERA_ADDR_M
lda x
sta cx16.VERA_ADDR_L
ldy cx16.r0L ; xbits
lda bits,y
ldy color
beq +
tsb cx16.VERA_DATA0
bra ++
+ trb cx16.VERA_DATA0
+
}}
}
}
; TODO mode 2,3
4 -> {
; lores 256c
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
%asm {{
stz cx16.VERA_CTRL
lda cx16.r1
ora #%00010000 ; enable auto-increment so next_pixel() can be used after this
sta cx16.VERA_ADDR_H
lda cx16.r0+1
sta cx16.VERA_ADDR_M
lda cx16.r0
sta cx16.VERA_ADDR_L
lda color
sta cx16.VERA_DATA0
}}
}
5 -> {
; highres monochrome
%asm {{
lda x
eor y
ora monochrome_dont_stipple_flag
and #1
}}
if_nz {
cx16.r0L = lsb(x) & 7 ; xbits
x /= 8
x += y*(640/8)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda x+1
sta cx16.VERA_ADDR_M
lda x
sta cx16.VERA_ADDR_L
ldy cx16.r0L ; xbits
lda bits,y
ldy color
beq +
tsb cx16.VERA_DATA0
bra ++
+ trb cx16.VERA_DATA0
+
}}
}
}
6 -> {
; highres 4c
; TODO also mostly usable for lores 4c?
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
cx16.r2L = lsb(x) & 3 ; xbits
color &= 3
color <<= shift4c[cx16.r2L]
%asm {{
stz cx16.VERA_CTRL
lda cx16.r1L
sta cx16.VERA_ADDR_H
lda cx16.r0H
sta cx16.VERA_ADDR_M
lda cx16.r0L
sta cx16.VERA_ADDR_L
ldy cx16.r2L ; xbits
lda mask4c,y
and cx16.VERA_DATA0
ora color
sta cx16.VERA_DATA0
}}
}
}
}
sub position(uword @zp x, uword y) {
ubyte bank
when active_mode {
1 -> {
; lores monochrome
cx16.r0 = y*(320/8) + x/8
cx16.vaddr(0, cx16.r0, 0, 1)
}
; TODO modes 2,3
4 -> {
; lores 256c
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
bank = lsb(cx16.r1)
cx16.vaddr(bank, cx16.r0, 0, 1)
}
5 -> {
; highres monochrome
cx16.r0 = y*(640/8) + x/8
cx16.vaddr(0, cx16.r0, 0, 1)
}
6 -> {
; highres 4c
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
bank = lsb(cx16.r1)
cx16.vaddr(bank, cx16.r0, 0, 1)
}
}
}
sub position2(uword @zp x, uword y, ubyte also_port_1) {
position(x, y)
if also_port_1 {
when active_mode {
1, 5 -> cx16.vaddr(0, cx16.r0, 1, 1)
; TODO modes 2, 3
4, 6 -> {
ubyte bank = lsb(cx16.r1)
cx16.vaddr(bank, cx16.r0, 1, 1)
}
}
}
}
inline asmsub next_pixel(ubyte color @A) {
; -- sets the next pixel byte to the graphics chip.
; for 8 bpp screens this will plot 1 pixel.
; for 1 bpp screens it will plot 8 pixels at once (color = bit pattern).
; for 2 bpp screens it will plot 4 pixels at once (color = bit pattern).
%asm {{
sta cx16.VERA_DATA0
}}
}
asmsub next_pixels(uword pixels @AY, uword amount @R0) clobbers(A, Y) {
; -- sets the next bunch of pixels from a prepared array of bytes.
; for 8 bpp screens this will plot 1 pixel per byte.
; for 1 bpp screens it will plot 8 pixels at once (colors are the bit patterns per byte).
; for 2 bpp screens it will plot 4 pixels at once (colors are the bit patterns per byte).
%asm {{
phx
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldx cx16.r0+1
beq +
ldy #0
- lda (P8ZP_SCRATCH_W1),y
sta cx16.VERA_DATA0
iny
bne -
inc P8ZP_SCRATCH_W1+1 ; next page of 256 pixels
dex
bne -
+ ldx cx16.r0 ; remaining pixels
beq +
ldy #0
- lda (P8ZP_SCRATCH_W1),y
sta cx16.VERA_DATA0
iny
dex
bne -
+ plx
}}
}
asmsub set_8_pixels_from_bits(ubyte bits @R0, ubyte oncolor @A, ubyte offcolor @Y) {
; this is only useful in 256 color mode where one pixel equals one byte value.
%asm {{
phx
ldx #8
- asl cx16.r0
bcc +
sta cx16.VERA_DATA0
bra ++
+ sty cx16.VERA_DATA0
+ dex
bne -
plx
rts
}}
}
const ubyte charset_orig_bank = $0
const uword charset_orig_addr = $f800 ; in bank 0, so $0f800
const ubyte charset_bank = $1
const uword charset_addr = $f000 ; in bank 1, so $1f000
sub text_charset(ubyte charset) {
; -- make a copy of the selected character set to use with text()
; the charset number is the same as for the cx16.screen_set_charset() ROM function.
; 1 = ISO charset, 2 = PETSCII uppercase+graphs, 3= PETSCII uppercase+lowercase.
cx16.screen_set_charset(charset, 0)
cx16.vaddr(charset_orig_bank, charset_orig_addr, 0, 1)
cx16.vaddr(charset_bank, charset_addr, 1, 1)
repeat 256*8 {
cx16.VERA_DATA1 = cx16.VERA_DATA0
}
}
sub text(uword @zp x, uword y, ubyte color, uword sctextptr) {
; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!).
; You must also have called text_charset() first to select and prepare the character set to use.
; NOTE: in monochrome (1bpp) screen modes, x position is currently constrained to multiples of 8 ! TODO allow per-pixel horizontal positioning
uword chardataptr
when active_mode {
1, 5 -> {
; monochrome mode, either resolution
cx16.r2 = 40
if active_mode==5
cx16.r2 = 80
while @(sctextptr) {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
cx16.vaddr(charset_bank, chardataptr, 1, 1)
position(x,y)
%asm {{
lda cx16.VERA_ADDR_H
and #%111 ; don't auto-increment, we have to do that manually because of the ora
sta cx16.VERA_ADDR_H
lda color
sta P8ZP_SCRATCH_B1
ldy #8
- lda P8ZP_SCRATCH_B1
bne + ; white color, plot normally
lda cx16.VERA_DATA1
eor #255 ; black color, keep only the other pixels
and cx16.VERA_DATA0
bra ++
+ lda cx16.VERA_DATA0
ora cx16.VERA_DATA1
+ sta cx16.VERA_DATA0
lda cx16.VERA_ADDR_L
clc
adc cx16.r2
sta cx16.VERA_ADDR_L
bcc +
inc cx16.VERA_ADDR_M
+ lda x
clc
adc #1
sta x
bcc +
inc x+1
+ dey
bne -
}}
sctextptr++
}
}
4 -> {
; lores 256c
while @(sctextptr) {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
cx16.vaddr(charset_bank, chardataptr, 1, 1)
repeat 8 {
; TODO rewrite this inner loop fully in assembly
position(x,y)
y++
%asm {{
phx
ldx #1
lda cx16.VERA_DATA1
sta P8ZP_SCRATCH_B1
ldy #8
- asl P8ZP_SCRATCH_B1
bcc +
stx cx16.VERA_DATA0 ; write a pixel
bra ++
+ lda cx16.VERA_DATA0 ; don't write a pixel, but do advance to the next address
+ dey
bne -
plx
}}
}
x+=8
y-=8
sctextptr++
}
}
6 -> {
; hires 4c
while @(sctextptr) {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
repeat 8 {
; TODO rewrite this inner loop fully in assembly
ubyte charbits = cx16.vpeek(charset_bank, chardataptr)
repeat 8 {
charbits <<= 1
if_cs
plot(x, y, color)
x++
}
x-=8
chardataptr++
y++
}
x+=8
y-=8
sctextptr++
}
}
}
}
asmsub cs_innerloop640() clobbers(Y) {
%asm {{
ldy #80
- stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
dey
bne -
rts
}}
}
asmsub addr_mul_24_for_highres_4c(uword yy @R2, uword xx @R3) clobbers(A, Y) -> uword @R0, uword @R1 {
; yy * 160 + xx/4 (24 bits calculation)
; 24 bits result is in r0 and r1L (highest byte)
%asm {{
ldy #5
- asl cx16.r2
rol cx16.r2+1
dey
bne -
lda cx16.r2
sta cx16.r0
lda cx16.r2+1
sta cx16.r0+1
asl cx16.r0
rol cx16.r0+1
asl cx16.r0
rol cx16.r0+1
; xx >>= 2 (xx=R3)
lsr cx16.r3+1
ror cx16.r3
lsr cx16.r3+1
ror cx16.r3
; add r2 and xx (r3) to r0 (24-bits)
stz cx16.r1
clc
lda cx16.r0
adc cx16.r2
sta cx16.r0
lda cx16.r0+1
adc cx16.r2+1
sta cx16.r0+1
bcc +
inc cx16.r1
+ clc
lda cx16.r0
adc cx16.r3
sta cx16.r0
lda cx16.r0+1
adc cx16.r3+1
sta cx16.r0+1
bcc +
inc cx16.r1
+
rts
}}
}
asmsub addr_mul_24_for_lores_256c(uword yy @R0, uword xx @AY) clobbers(A) -> uword @R0, ubyte @R1 {
; yy * 320 + xx (24 bits calculation)
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda cx16.r0
sta P8ZP_SCRATCH_B1
lda cx16.r0+1
sta cx16.r1
sta P8ZP_SCRATCH_REG
lda cx16.r0
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
sta cx16.r0
lda P8ZP_SCRATCH_B1
clc
adc P8ZP_SCRATCH_REG
sta cx16.r0+1
bcc +
inc cx16.r1
+ ; now add the value to this 24-bits number
lda cx16.r0
clc
adc P8ZP_SCRATCH_W1
sta cx16.r0
lda cx16.r0+1
adc P8ZP_SCRATCH_W1+1
sta cx16.r0+1
bcc +
inc cx16.r1
+ lda cx16.r1
rts
}}
}
}

View File

@ -1,10 +1,12 @@
%target cx16
%import syslib
%import textio
; bitmap pixel graphics module for the CommanderX16
; Bitmap pixel graphics module for the CommanderX16
; wraps the graphics functions that are in ROM.
; only black/white monchrome 320x200 for now.
; only black/white monochrome 320x200 for now. (i.e. truncated at the bottom)
; For full-screen 640x480 or 320x240 graphics, use the "gfx2" module instead. (but that is Cx16-specific)
; Note: there is no color palette manipulation here, you have to do that yourself or use the "palette" module.
graphics {
const uword WIDTH = 320
@ -13,15 +15,14 @@ graphics {
sub enable_bitmap_mode() {
; enable bitmap screen, erase it and set colors to black/white.
void cx16.screen_set_mode($80)
cx16.r0 = 0
cx16.GRAPH_init()
cx16.GRAPH_init(0)
clear_screen(1, 0)
}
sub disable_bitmap_mode() {
; enables text mode, erase the text screen, color white
void cx16.screen_set_mode(2)
txt.fill_screen(' ', 1) ; TODO doesn't seem to fully clear the text screen after returning from gfx mode
txt.fill_screen(' ', 1) ; doesn't seem to fully clear the text screen after returning from gfx mode
}
@ -31,11 +32,25 @@ graphics {
}
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
cx16.r0 = x1
cx16.r1 = y1
cx16.r2 = x2
cx16.r3 = y2
cx16.GRAPH_draw_line()
cx16.GRAPH_draw_line(x1, y1, x2, y2)
}
sub fillrect(uword x, uword y, uword width, uword height) {
cx16.GRAPH_draw_rect(x, y, width, height, 0, 1)
}
sub rect(uword x, uword y, uword width, uword height) {
cx16.GRAPH_draw_rect(x, y, width, height, 0, 0)
}
sub horizontal_line(uword x, uword y, uword length) {
if length
cx16.GRAPH_draw_line(x, y, x+length-1, y)
}
sub vertical_line(uword x, uword y, uword height) {
if height
cx16.GRAPH_draw_line(x, y, x, y+height-1)
}
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
@ -43,122 +58,80 @@ graphics {
;cx16.r1 = ycenter - radius/2
;cx16.r2 = radius*2
;cx16.r3 = radius*2
;cx16.GRAPH_draw_oval(false) ; TODO currently is not implemented on cx16, does a BRK
;cx16.GRAPH_draw_oval(false) ; currently this call is not implemented on cx16, does a BRK
; Midpoint algorithm
if radius==0
return
ubyte @zp xx = radius
ubyte @zp yy = 0
byte @zp decisionOver2 = 1-xx as byte
word @zp decisionOver2 = (1 as word)-xx
while xx>=yy {
cx16.r0 = xcenter + xx
cx16.r1 = ycenter + yy
cx16.FB_cursor_position()
cx16.FB_cursor_position2()
cx16.FB_set_pixel(1)
cx16.r0 = xcenter - xx
cx16.FB_cursor_position()
cx16.FB_cursor_position2()
cx16.FB_set_pixel(1)
cx16.r0 = xcenter + xx
cx16.r1 = ycenter - yy
cx16.FB_cursor_position()
cx16.FB_cursor_position2()
cx16.FB_set_pixel(1)
cx16.r0 = xcenter - xx
cx16.FB_cursor_position()
cx16.FB_cursor_position2()
cx16.FB_set_pixel(1)
cx16.r0 = xcenter + yy
cx16.r1 = ycenter + xx
cx16.FB_cursor_position()
cx16.FB_cursor_position2()
cx16.FB_set_pixel(1)
cx16.r0 = xcenter - yy
cx16.FB_cursor_position()
cx16.FB_cursor_position2()
cx16.FB_set_pixel(1)
cx16.r0 = xcenter + yy
cx16.r1 = ycenter - xx
cx16.FB_cursor_position()
cx16.FB_cursor_position2()
cx16.FB_set_pixel(1)
cx16.r0 = xcenter - yy
cx16.FB_cursor_position()
cx16.FB_cursor_position2()
cx16.FB_set_pixel(1)
yy++
if decisionOver2<=0 {
decisionOver2 += 2*yy+1
decisionOver2 += (yy as word)*2+1
} else {
xx--
decisionOver2 += 2*(yy-xx)+1
decisionOver2 += (yy as word -xx)*2+1
}
}
}
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
; cx16.r0 = xcenter - radius/2
; cx16.r1 = ycenter - radius/2
; cx16.r2 = radius*2
; cx16.r3 = radius*2
; cx16.GRAPH_draw_oval(true) ; TODO currently is not implemented on cx16, does a BRK
if radius==0
return
ubyte @zp yy = 0
word decisionOver2 = (1 as word)-radius
ubyte xx = radius
ubyte yy = 0
byte decisionOver2 = 1-xx as byte
while xx>=yy {
ubyte ycenter_plus_yy = ycenter + yy
ubyte ycenter_min_yy = ycenter - yy
ubyte ycenter_plus_xx = ycenter + xx
ubyte ycenter_min_xx = ycenter - xx
uword @zp plotx
for plotx in xcenter to xcenter+xx {
cx16.r0 = plotx
cx16.r1 = ycenter_plus_yy
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
cx16.r1 = ycenter_min_yy
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
}
for plotx in xcenter-xx to xcenter-1 {
cx16.r0 = plotx
cx16.r1 = ycenter_plus_yy
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
cx16.r1 = ycenter_min_yy
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
}
for plotx in xcenter to xcenter+yy {
cx16.r0 = plotx
cx16.r1 = ycenter_plus_xx
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
cx16.r1 = ycenter_min_xx
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
}
for plotx in xcenter-yy to xcenter {
cx16.r0 = plotx
cx16.r1 = ycenter_plus_xx
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
cx16.r1 = ycenter_min_xx
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
}
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*2+1)
horizontal_line(xcenter-radius, ycenter-yy, radius*2+1)
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
yy++
if decisionOver2<=0
decisionOver2 += 2*yy+1
decisionOver2 += (yy as word)*2+1
else {
xx--
decisionOver2 += 2*(yy-xx)+1
radius--
decisionOver2 += (yy as word -radius)*2+1
}
}
}
sub plot(uword plotx, ubyte ploty) {
cx16.r0 = plotx
cx16.r1 = ploty
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
inline asmsub plot(uword plotx @R0, uword ploty @R1) clobbers(A, X, Y) {
%asm {{
jsr cx16.FB_cursor_position
lda #1
jsr cx16.FB_set_pixel
}}
}
}

View File

@ -0,0 +1,189 @@
; Manipulate the Commander X16's display color palette.
; Should you want to restore the default palette, you have to reinitialize the Vera yourself.
palette {
uword vera_palette_ptr
ubyte c
sub set_color(ubyte index, uword color) {
vera_palette_ptr = $fa00+(index as uword * 2)
cx16.vpoke(1, vera_palette_ptr, lsb(color))
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(color))
}
sub set_rgb4(uword palette_bytes_ptr, uword num_colors) {
; 2 bytes per color entry, the Vera uses this, but the R/GB bytes order is swapped
vera_palette_ptr = $fa00
repeat num_colors {
cx16.vpoke(1, vera_palette_ptr+1, @(palette_bytes_ptr))
palette_bytes_ptr++
cx16.vpoke(1, vera_palette_ptr, @(palette_bytes_ptr))
palette_bytes_ptr++
vera_palette_ptr+=2
}
}
sub set_rgb(uword palette_words_ptr, uword num_colors) {
; 1 word per color entry (in little endian format so $gb0r)
vera_palette_ptr = $fa00
repeat num_colors*2 {
cx16.vpoke(1, vera_palette_ptr, @(palette_words_ptr))
palette_words_ptr++
vera_palette_ptr++
}
}
sub set_rgb8(uword palette_bytes_ptr, uword num_colors) {
; 3 bytes per color entry, adjust color depth from 8 to 4 bits per channel.
vera_palette_ptr = $fa00
ubyte red
ubyte greenblue
repeat num_colors {
red = @(palette_bytes_ptr) >> 4
palette_bytes_ptr++
greenblue = @(palette_bytes_ptr) & %11110000
palette_bytes_ptr++
greenblue |= @(palette_bytes_ptr) >> 4 ; add Blue
palette_bytes_ptr++
cx16.vpoke(1, vera_palette_ptr, greenblue)
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, red)
vera_palette_ptr++
}
}
sub set_monochrome(uword screencolorRGB, uword drawcolorRGB) {
vera_palette_ptr = $fa00
cx16.vpoke(1, vera_palette_ptr, lsb(screencolorRGB)) ; G,B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(screencolorRGB)) ; R
vera_palette_ptr++
repeat 255 {
cx16.vpoke(1, vera_palette_ptr, lsb(drawcolorRGB)) ; G,B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(drawcolorRGB)) ; R
vera_palette_ptr++
}
}
sub set_all_black() {
set_monochrome($000, $000)
}
sub set_all_white() {
set_monochrome($fff, $fff)
}
sub set_grayscale() {
vera_palette_ptr = $fa00
repeat 16 {
c=0
repeat 16 {
cx16.vpoke(1, vera_palette_ptr, c)
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, c)
vera_palette_ptr++
c += $11
}
}
}
uword[] C64_colorpalette_dark = [ ; this is a darker palette with more contrast
$000, ; 0 = black
$FFF, ; 1 = white
$632, ; 2 = red
$7AB, ; 3 = cyan
$638, ; 4 = purple
$584, ; 5 = green
$327, ; 6 = blue
$BC6, ; 7 = yellow
$642, ; 8 = orange
$430, ; 9 = brown
$965, ; 10 = light red
$444, ; 11 = dark grey
$666, ; 12 = medium grey
$9D8, ; 13 = light green
$65B, ; 14 = light blue
$999 ; 15 = light grey
]
uword[] C64_colorpalette_pepto = [ ; # this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
$000, ; 0 = black
$FFF, ; 1 = white
$833, ; 2 = red
$7cc, ; 3 = cyan
$839, ; 4 = purple
$5a4, ; 5 = green
$229, ; 6 = blue
$ef7, ; 7 = yellow
$852, ; 8 = orange
$530, ; 9 = brown
$c67, ; 10 = light red
$444, ; 11 = dark grey
$777, ; 12 = medium grey
$af9, ; 13 = light green
$76e, ; 14 = light blue
$bbb ; 15 = light grey
]
uword[] C64_colorpalette_light = [ ; this is a lighter palette
$000, ; 0 = black
$FFF, ; 1 = white
$944, ; 2 = red
$7CC, ; 3 = cyan
$95A, ; 4 = purple
$6A5, ; 5 = green
$549, ; 6 = blue
$CD8, ; 7 = yellow
$963, ; 8 = orange
$650, ; 9 = brown
$C77, ; 10 = light red
$666, ; 11 = dark grey
$888, ; 12 = medium grey
$AE9, ; 13 = light green
$87C, ; 14 = light blue
$AAA ; 15 = light grey
]
sub set_c64pepto() {
vera_palette_ptr = $fa00
repeat 16 {
for c in 0 to 15 {
uword cc = C64_colorpalette_pepto[c]
cx16.vpoke(1, vera_palette_ptr, lsb(cc)) ; G, B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(cc)) ; R
vera_palette_ptr++
}
}
}
sub set_c64light() {
vera_palette_ptr = $fa00
repeat 16 {
for c in 0 to 15 {
uword cc = C64_colorpalette_light[c]
cx16.vpoke(1, vera_palette_ptr, lsb(cc)) ; G, B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(cc)) ; R
vera_palette_ptr++
}
}
}
sub set_c64dark() {
vera_palette_ptr = $fa00
repeat 16 {
for c in 0 to 15 {
uword cc = C64_colorpalette_dark[c]
cx16.vpoke(1, vera_palette_ptr, lsb(cc)) ; G, B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(cc)) ; R
vera_palette_ptr++
}
}
}
}

View File

@ -5,9 +5,6 @@
;
; indent format: TABS, size=8
%target cx16
c64 {
; ---- kernal routines, these are the same as on the Commodore-64 (hence the same block name) ----
@ -24,7 +21,7 @@ romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; re
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer. NOTE: as a Cx16 extension, also returns the number of RAM memory banks in register A ! See cx16.numbanks()
romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
@ -35,7 +32,7 @@ romsub $FFAE = UNLSN() clobbers(A) ; command serial
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
@ -44,10 +41,10 @@ romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
@ -56,14 +53,46 @@ romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
; ---- utility
asmsub STOP2() -> ubyte @A {
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
%asm {{
phx
jsr c64.STOP
beq +
plx
lda #0
rts
+ plx
lda #1
rts
}}
}
asmsub RDTIM16() -> uword @AY {
; -- like RDTIM() but only returning the lower 16 bits in AY for convenience
%asm {{
phx
jsr c64.RDTIM
pha
txa
tay
pla
plx
rts
}}
}
}
cx16 {
; 65c02 hardware vectors:
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
; irq and hardware vectors:
&uword CINV = $0314 ; IRQ vector (in ram)
&uword NMI_VEC = $FFFA ; 65c02 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 65c02 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 65c02 interrupt vector, determined by the kernal if banked in
; the sixteen virtual 16-bit registers
@ -84,52 +113,87 @@ cx16 {
&uword r14 = $001e
&uword r15 = $0020
&ubyte r0L = $0002
&ubyte r1L = $0004
&ubyte r2L = $0006
&ubyte r3L = $0008
&ubyte r4L = $000a
&ubyte r5L = $000c
&ubyte r6L = $000e
&ubyte r7L = $0010
&ubyte r8L = $0012
&ubyte r9L = $0014
&ubyte r10L = $0016
&ubyte r11L = $0018
&ubyte r12L = $001a
&ubyte r13L = $001c
&ubyte r14L = $001e
&ubyte r15L = $0020
&ubyte r0H = $0003
&ubyte r1H = $0005
&ubyte r2H = $0007
&ubyte r3H = $0009
&ubyte r4H = $000b
&ubyte r5H = $000d
&ubyte r6H = $000f
&ubyte r7H = $0011
&ubyte r8H = $0013
&ubyte r9H = $0015
&ubyte r10H = $0017
&ubyte r11H = $0019
&ubyte r12H = $001b
&ubyte r13H = $001d
&ubyte r14H = $001f
&ubyte r15H = $0021
; VERA registers
const uword VERA_BASE = $9F20
&ubyte VERA_ADDR_L = VERA_BASE + $0000
&ubyte VERA_ADDR_M = VERA_BASE + $0001
&ubyte VERA_ADDR_H = VERA_BASE + $0002
&ubyte VERA_DATA0 = VERA_BASE + $0003
&ubyte VERA_DATA1 = VERA_BASE + $0004
&ubyte VERA_CTRL = VERA_BASE + $0005
&ubyte VERA_IEN = VERA_BASE + $0006
&ubyte VERA_ISR = VERA_BASE + $0007
&ubyte VERA_IRQ_LINE_L = VERA_BASE + $0008
&ubyte VERA_DC_VIDEO = VERA_BASE + $0009
&ubyte VERA_DC_HSCALE = VERA_BASE + $000A
&ubyte VERA_DC_VSCALE = VERA_BASE + $000B
&ubyte VERA_DC_BORDER = VERA_BASE + $000C
&ubyte VERA_DC_HSTART = VERA_BASE + $0009
&ubyte VERA_DC_HSTOP = VERA_BASE + $000A
&ubyte VERA_DC_VSTART = VERA_BASE + $000B
&ubyte VERA_DC_VSTOP = VERA_BASE + $000C
&ubyte VERA_L0_CONFIG = VERA_BASE + $000D
&ubyte VERA_L0_MAPBASE = VERA_BASE + $000E
&ubyte VERA_L0_TILEBASE = VERA_BASE + $000F
&ubyte VERA_L0_HSCROLL_L = VERA_BASE + $0010
&ubyte VERA_L0_HSCROLL_H = VERA_BASE + $0011
&ubyte VERA_L0_VSCROLL_L = VERA_BASE + $0012
&ubyte VERA_L0_VSCROLL_H = VERA_BASE + $0013
&ubyte VERA_L1_CONFIG = VERA_BASE + $0014
&ubyte VERA_L1_MAPBASE = VERA_BASE + $0015
&ubyte VERA_L1_TILEBASE = VERA_BASE + $0016
&ubyte VERA_L1_HSCROLL_L = VERA_BASE + $0017
&ubyte VERA_L1_HSCROLL_H = VERA_BASE + $0018
&ubyte VERA_L1_VSCROLL_L = VERA_BASE + $0019
&ubyte VERA_L1_VSCROLL_H = VERA_BASE + $001A
&ubyte VERA_AUDIO_CTRL = VERA_BASE + $001B
&ubyte VERA_AUDIO_RATE = VERA_BASE + $001C
&ubyte VERA_AUDIO_DATA = VERA_BASE + $001D
&ubyte VERA_SPI_DATA = VERA_BASE + $001E
&ubyte VERA_SPI_CTRL = VERA_BASE + $001F
const uword VERA_BASE = $9F20
&ubyte VERA_ADDR_L = VERA_BASE + $0000
&ubyte VERA_ADDR_M = VERA_BASE + $0001
&ubyte VERA_ADDR_H = VERA_BASE + $0002
&ubyte VERA_DATA0 = VERA_BASE + $0003
&ubyte VERA_DATA1 = VERA_BASE + $0004
&ubyte VERA_CTRL = VERA_BASE + $0005
&ubyte VERA_IEN = VERA_BASE + $0006
&ubyte VERA_ISR = VERA_BASE + $0007
&ubyte VERA_IRQ_LINE_L = VERA_BASE + $0008
&ubyte VERA_DC_VIDEO = VERA_BASE + $0009
&ubyte VERA_DC_HSCALE = VERA_BASE + $000A
&ubyte VERA_DC_VSCALE = VERA_BASE + $000B
&ubyte VERA_DC_BORDER = VERA_BASE + $000C
&ubyte VERA_DC_HSTART = VERA_BASE + $0009
&ubyte VERA_DC_HSTOP = VERA_BASE + $000A
&ubyte VERA_DC_VSTART = VERA_BASE + $000B
&ubyte VERA_DC_VSTOP = VERA_BASE + $000C
&ubyte VERA_L0_CONFIG = VERA_BASE + $000D
&ubyte VERA_L0_MAPBASE = VERA_BASE + $000E
&ubyte VERA_L0_TILEBASE = VERA_BASE + $000F
&ubyte VERA_L0_HSCROLL_L = VERA_BASE + $0010
&ubyte VERA_L0_HSCROLL_H = VERA_BASE + $0011
&ubyte VERA_L0_VSCROLL_L = VERA_BASE + $0012
&ubyte VERA_L0_VSCROLL_H = VERA_BASE + $0013
&ubyte VERA_L1_CONFIG = VERA_BASE + $0014
&ubyte VERA_L1_MAPBASE = VERA_BASE + $0015
&ubyte VERA_L1_TILEBASE = VERA_BASE + $0016
&ubyte VERA_L1_HSCROLL_L = VERA_BASE + $0017
&ubyte VERA_L1_HSCROLL_H = VERA_BASE + $0018
&ubyte VERA_L1_VSCROLL_L = VERA_BASE + $0019
&ubyte VERA_L1_VSCROLL_H = VERA_BASE + $001A
&ubyte VERA_AUDIO_CTRL = VERA_BASE + $001B
&ubyte VERA_AUDIO_RATE = VERA_BASE + $001C
&ubyte VERA_AUDIO_DATA = VERA_BASE + $001D
&ubyte VERA_SPI_DATA = VERA_BASE + $001E
&ubyte VERA_SPI_CTRL = VERA_BASE + $001F
; VERA_PSG_BASE = $1F9C0
; VERA_PALETTE_BASE = $1FA00
; VERA_SPRITES_BASE = $1FC00
; I/O
const uword via1 = $9f60 ;VIA 6522 #1
const uword via1 = $9f00 ;VIA 6522 #1
&ubyte d1prb = via1+0
&ubyte d1pra = via1+1
&ubyte d1ddrb = via1+2
@ -147,23 +211,28 @@ cx16 {
&ubyte d1ier = via1+14
&ubyte d1ora = via1+15
const uword via2 = $9f70 ;VIA 6522 #2
&ubyte d2prb =via2+0
&ubyte d2pra =via2+1
&ubyte d2ddrb =via2+2
&ubyte d2ddra =via2+3
&ubyte d2t1l =via2+4
&ubyte d2t1h =via2+5
&ubyte d2t1ll =via2+6
&ubyte d2t1lh =via2+7
&ubyte d2t2l =via2+8
&ubyte d2t2h =via2+9
&ubyte d2sr =via2+10
&ubyte d2acr =via2+11
&ubyte d2pcr =via2+12
&ubyte d2ifr =via2+13
&ubyte d2ier =via2+14
&ubyte d2ora =via2+15
const uword via2 = $9f10 ;VIA 6522 #2
&ubyte d2prb = via2+0
&ubyte d2pra = via2+1
&ubyte d2ddrb = via2+2
&ubyte d2ddra = via2+3
&ubyte d2t1l = via2+4
&ubyte d2t1h = via2+5
&ubyte d2t1ll = via2+6
&ubyte d2t1lh = via2+7
&ubyte d2t2l = via2+8
&ubyte d2t2h = via2+9
&ubyte d2sr = via2+10
&ubyte d2acr = via2+11
&ubyte d2pcr = via2+12
&ubyte d2ifr = via2+13
&ubyte d2ier = via2+14
&ubyte d2ora = via2+15
&ubyte ym2151adr = $9f40
&ubyte ym2151dat = $9f41
const uword extdev = $9f60
; ---- Commander X-16 additions on top of C64 kernal routines ----
@ -190,72 +259,296 @@ romsub $ff6b = mouse_get(ubyte zpdataptr @X) clobbers(A)
romsub $ff71 = mouse_scan() clobbers(A, X, Y)
romsub $ff53 = joystick_scan() clobbers(A, X, Y)
romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y
romsub $ff4d = clock_set_date_time() clobbers(A, X, Y) ; args: r0, r1, r2, r3L
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) ; outout args: r0, r1, r2, r3L
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()
; TODO specify the correct clobbers for alle these functions below, we now assume all 3 regs are clobbered
; high level graphics & fonts
romsub $ff20 = GRAPH_init() clobbers(A,X,Y) ; uses vectors=r0
romsub $ff20 = GRAPH_init(uword vectors @R0) clobbers(A,X,Y)
romsub $ff23 = GRAPH_clear() clobbers(A,X,Y)
romsub $ff26 = GRAPH_set_window() clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3
romsub $ff26 = GRAPH_set_window(uword x @R0, uword y @R1, uword width @R2, uword height @R3) clobbers(A,X,Y)
romsub $ff29 = GRAPH_set_colors(ubyte stroke @A, ubyte fill @X, ubyte background @Y) clobbers (A,X,Y)
romsub $ff2c = GRAPH_draw_line() clobbers(A,X,Y) ; uses x1=r0, y1=r1, x2=r2, y2=r3
romsub $ff2f = GRAPH_draw_rect(ubyte fill @Pc) clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3, cornerradius=r4
romsub $ff32 = GRAPH_move_rect() clobbers(A,X,Y) ; uses sx=r0, sy=r1, tx=r2, ty=r3, width=r4, height=r5
romsub $ff35 = GRAPH_draw_oval(ubyte fill @Pc) clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3
romsub $ff38 = GRAPH_draw_image() clobbers(A,X,Y) ; uses x=r0, y=r1, ptr=r2, width=r3, height=r4
romsub $ff3b = GRAPH_set_font() clobbers(A,X,Y) ; uses ptr=r0
romsub $ff2c = GRAPH_draw_line(uword x1 @R0, uword y1 @R1, uword x2 @R2, uword y2 @R3) clobbers(A,X,Y)
romsub $ff2f = GRAPH_draw_rect(uword x @R0, uword y @R1, uword width @R2, uword height @R3, uword cornerradius @R4, ubyte fill @Pc) clobbers(A,X,Y)
romsub $ff32 = GRAPH_move_rect(uword sx @R0, uword sy @R1, uword tx @R2, uword ty @R3, uword width @R4, uword height @R5) clobbers(A,X,Y)
romsub $ff35 = GRAPH_draw_oval(uword x @R0, uword y @R1, uword width @R2, uword height @R3, ubyte fill @Pc) clobbers(A,X,Y)
romsub $ff38 = GRAPH_draw_image(uword x @R0, uword y @R1, uword ptr @R2, uword width @R3, uword height @R4) clobbers(A,X,Y)
romsub $ff3b = GRAPH_set_font(uword fontptr @R0) clobbers(A,X,Y)
romsub $ff3e = GRAPH_get_char_size(ubyte baseline @A, ubyte width @X, ubyte height_or_style @Y, ubyte is_control @Pc) clobbers(A,X,Y)
romsub $ff41 = GRAPH_put_char(ubyte char @A) clobbers(A,X,Y) ; uses x=r0, y=r1
romsub $ff41 = GRAPH_put_char(uword x @R0, uword y @R1, ubyte char @A) clobbers(A,X,Y)
romsub $ff41 = GRAPH_put_next_char(ubyte char @A) clobbers(A,X,Y) ; alias for the routine above that doesn't reset the position of the initial character
; framebuffer
romsub $fef6 = FB_init() clobbers(A,X,Y)
romsub $fef9 = FB_get_info() clobbers(X,Y) -> byte @A ; also outputs width=r0, height=r1
romsub $fefc = FB_set_palette(ubyte index @A, ubyte bytecount @X) clobbers(A,X,Y) ; also uses pointer=r0
romsub $feff = FB_cursor_position() clobbers(A,X,Y) ; uses x=r0, y=r1
romsub $ff02 = FB_cursor_next_line() clobbers(A,X,Y) ; uses x=r0
romsub $fef9 = FB_get_info() clobbers(X,Y) -> byte @A, uword @R0, uword @R1 ; width=r0, height=r1
romsub $fefc = FB_set_palette(uword pointer @R0, ubyte index @A, ubyte bytecount @X) clobbers(A,X,Y)
romsub $feff = FB_cursor_position(uword x @R0, uword y @R1) clobbers(A,X,Y)
romsub $feff = FB_cursor_position2() clobbers(A,X,Y) ; alias for the previous routine, but avoiding having to respecify both x and y every time
romsub $ff02 = FB_cursor_next_line(uword x @R0) clobbers(A,X,Y)
romsub $ff05 = FB_get_pixel() clobbers(X,Y) -> ubyte @A
romsub $ff08 = FB_get_pixels() clobbers(A,X,Y) ; uses ptr=r0, count=r1
romsub $ff08 = FB_get_pixels(uword pointer @R0, uword count @R1) clobbers(A,X,Y)
romsub $ff0b = FB_set_pixel(ubyte color @A) clobbers(A,X,Y)
romsub $ff0e = FB_set_pixels() clobbers(A,X,Y) ; uses ptr=r0, count=r1
romsub $ff0e = FB_set_pixels(uword pointer @R0, uword count @R1) clobbers(A,X,Y)
romsub $ff11 = FB_set_8_pixels(ubyte pattern @A, ubyte color @X) clobbers(A,X,Y)
romsub $ff14 = FB_set_8_pixels_opaque(ubyte pattern @A, ubyte color1 @X, ubyte color2 @Y) clobbers(A,X,Y) ; also uses mask=r0L
romsub $ff17 = FB_fill_pixels(ubyte color @A) clobbers(A,X,Y) ; also uses count=r0, step=r1
romsub $ff1a = FB_filter_pixels() clobbers(A,X,Y) ; uses ptr=r0, count=r1
romsub $ff1d = FB_move_pixels() clobbers(A,X,Y) ; uses sx=r0, sy=r1, tx=r2, ty=r3, count=r4
romsub $ff14 = FB_set_8_pixels_opaque(ubyte pattern @R0, ubyte mask @A, ubyte color1 @X, ubyte color2 @Y) clobbers(A,X,Y)
romsub $ff17 = FB_fill_pixels(uword count @R0, uword pstep @R1, ubyte color @A) clobbers(A,X,Y)
romsub $ff1a = FB_filter_pixels(uword pointer @ R0, uword count @R1) clobbers(A,X,Y)
romsub $ff1d = FB_move_pixels(uword sx @R0, uword sy @R1, uword tx @R2, uword ty @R3, uword count @R4) clobbers(A,X,Y)
; misc
romsub $fef0 = sprite_set_image(ubyte number @A, ubyte width @X, ubyte height @Y, ubyte apply_mask @Pc) clobbers(A,X,Y) -> ubyte @Pc ; also uses pixels=r0, mask=r1, bpp=r2L
romsub $fef3 = sprite_set_position(ubyte number @A) clobbers(A,X,Y) ; also uses x=r0 and y=r1
romsub $fee4 = memory_fill(ubyte value @A) clobbers(A,X,Y) ; uses address=r0, num_bytes=r1
romsub $fee7 = memory_copy() clobbers(A,X,Y) ; uses source=r0, target=r1, num_bytes=r2
romsub $feea = memory_crc() clobbers(A,X,Y) ; uses address=r0, num_bytes=r1 result->r2
romsub $feed = memory_decompress() clobbers(A,X,Y) ; uses input=r0, output=r1 result->r1
romsub $fedb = console_init() clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3
romsub $fef0 = sprite_set_image(uword pixels @R0, uword mask @R1, ubyte bpp @R2, ubyte number @A, ubyte width @X, ubyte height @Y, ubyte apply_mask @Pc) clobbers(A,X,Y) -> ubyte @Pc
romsub $fef3 = sprite_set_position(uword x @R0, uword y @R1, ubyte number @A) clobbers(A,X,Y)
romsub $fee4 = memory_fill(uword address @R0, uword num_bytes @R1, ubyte value @A) clobbers(A,X,Y)
romsub $fee7 = memory_copy(uword source @R0, uword target @R1, uword num_bytes @R2) clobbers(A,X,Y)
romsub $feea = memory_crc(uword address @R0, uword num_bytes @R1) clobbers(A,X,Y) -> uword @R2
romsub $feed = memory_decompress(uword input @R0, uword output @R1) clobbers(A,X,Y) -> uword @R1 ; last address +1 is result in R1
romsub $fedb = console_init(uword x @R0, uword y @R1, uword width @R2, uword height @R3) clobbers(A,X,Y)
romsub $fede = console_put_char(ubyte char @A, ubyte wrapping @Pc) clobbers(A,X,Y)
romsub $fee1 = console_get_char() clobbers(X,Y) -> ubyte @A
romsub $fed8 = console_put_image() clobbers(A,X,Y) ; uses ptr=r0, width=r1, height=r2
romsub $fed5 = console_set_paging_message() clobbers(A,X,Y) ; uses messageptr=r0
romsub $fed8 = console_put_image(uword pointer @R0, uword width @R1, uword height @R2) clobbers(A,X,Y)
romsub $fed5 = console_set_paging_message(uword msgptr @R0) clobbers(A,X,Y)
romsub $fed2 = kbdbuf_put(ubyte key @A) clobbers(A,X,Y)
romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
romsub $fecc = monitor() clobbers(A,X,Y)
; ---- end of kernal routines ----
asmsub init_system() {
; ---- utilities -----
inline asmsub rombank(ubyte rombank @A) {
; -- set the rom banks
%asm {{
sta $01 ; rom bank register (v39+, used to be cx16.d1prb $9f60 in v38)
}}
}
inline asmsub rambank(ubyte rambank @A) {
; -- set the ram bank
%asm {{
sta $00 ; ram bank register (v39+, used to be cx16.d1pra $9f61 in v38)
}}
}
asmsub numbanks() -> ubyte @A {
; -- uses MEMTOP's cx16 extension to query the number of available RAM banks. (each is 8 Kb)
%asm {{
phx
sec
jsr c64.MEMTOP
plx
rts
}}
}
asmsub vpeek(ubyte bank @A, uword address @XY) -> ubyte @A {
; -- get a byte from VERA's video memory
; note: inefficient when reading multiple sequential bytes!
%asm {{
pha
lda #1
sta cx16.VERA_CTRL
pla
and #1
sta cx16.VERA_ADDR_H
sty cx16.VERA_ADDR_M
stx cx16.VERA_ADDR_L
lda cx16.VERA_DATA1
rts
}}
}
asmsub vaddr(ubyte bank @A, uword address @R0, ubyte addrsel @R1, byte autoIncrOrDecrByOne @Y) clobbers(A) {
; -- setup the VERA's data address register 0 or 1
%asm {{
and #1
pha
lda cx16.r1
and #1
sta cx16.VERA_CTRL
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
pla
cpy #0
bmi ++
beq +
ora #%00010000
+ sta cx16.VERA_ADDR_H
rts
+ ora #%00011000
sta cx16.VERA_ADDR_H
rts
}}
}
asmsub vpoke(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers(A) {
; -- write a single byte to VERA's video memory
; note: inefficient when writing multiple sequential bytes!
%asm {{
stz cx16.VERA_CTRL
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
sty cx16.VERA_DATA0
rts
}}
}
asmsub vpoke_or(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers (A) {
; -- or a single byte to the value already in the VERA's video memory at that location
; note: inefficient when writing multiple sequential bytes!
%asm {{
stz cx16.VERA_CTRL
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
tya
ora cx16.VERA_DATA0
sta cx16.VERA_DATA0
rts
}}
}
asmsub vpoke_and(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers(A) {
; -- and a single byte to the value already in the VERA's video memory at that location
; note: inefficient when writing multiple sequential bytes!
%asm {{
stz cx16.VERA_CTRL
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
tya
and cx16.VERA_DATA0
sta cx16.VERA_DATA0
rts
}}
}
asmsub vpoke_xor(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers (A) {
; -- xor a single byte to the value already in the VERA's video memory at that location
; note: inefficient when writing multiple sequential bytes!
%asm {{
stz cx16.VERA_CTRL
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
tya
eor cx16.VERA_DATA0
sta cx16.VERA_DATA0
rts
}}
}
asmsub vload(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
; -- like the basic command VLOAD "filename",device,bank,address
; loads a file into video memory in the given bank:address, returns success in A
; !! NOTE !! the V38 ROMs contain a bug in the LOAD code that makes the load address not work correctly,
; it works fine when loading from local filesystem
%asm {{
; -- load a file into video ram
phx
pha
tya
tax
lda #1
ldy #0
jsr c64.SETLFS
lda cx16.r0
ldy cx16.r0+1
jsr prog8_lib.strlen
tya
ldx cx16.r0
ldy cx16.r0+1
jsr c64.SETNAM
pla
clc
adc #2
ldx cx16.r1
ldy cx16.r1+1
stz P8ZP_SCRATCH_B1
jsr c64.LOAD
bcs +
inc P8ZP_SCRATCH_B1
+ jsr c64.CLRCHN
lda #1
jsr c64.CLOSE
plx
lda P8ZP_SCRATCH_B1
rts
}}
}
inline asmsub joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX {
; convenience routine to get the joystick state without requiring inline assembly that deals with the multiple return values.
; Also disables interrupts to avoid the IRQ race condition mentioned here: https://github.com/commanderx16/x16-rom/issues/203
; TODO once that issue is resolved, this routine can be redefined as: romsub $ff56 = joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX
%asm {{
sei
jsr cx16.joystick_get
cli
}}
}
sub FB_set_pixels_from_buf(uword buffer, uword count) {
%asm {{
; -- This is replacement code for the normal FB_set_pixels subroutine in ROM
; However that routine contains a bug in the current v38 ROM that makes it crash when count > 255.
; So the code below replaces that. Once the ROM is patched this routine is no longer necessary.
; See https://github.com/commanderx16/x16-rom/issues/179
phx
lda buffer
ldy buffer+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
jsr _pixels
plx
rts
_pixels lda count+1
beq +
ldx #0
- jsr _loop
inc P8ZP_SCRATCH_W1+1
dec count+1
bne -
+ ldx count
_loop ldy #0
- lda (P8ZP_SCRATCH_W1),y
sta cx16.VERA_DATA0
iny
dex
bne -
rts
}}
}
; ---- system stuff -----
asmsub init_system() {
; Initializes the machine to a sane starting state.
; Called automatically by the loader program logic.
%asm {{
sei
cld
;stz $00
;stz $01
;stz d1prb ; select rom bank 0
lda #$80
sta VERA_CTRL
stz $01 ; select rom bank 0 (enable kernal)
jsr c64.IOINIT
jsr c64.RESTOR
jsr c64.CINT
@ -277,15 +570,311 @@ asmsub init_system() {
}}
}
asmsub reset_system() {
; Soft-reset the system back to Basic prompt.
asmsub init_system_phase2() {
%asm {{
sei
lda #14
sta $01
stz cx16.d1prb ; bank the kernal in
jmp (cx16.RESET_VEC)
lda cx16.CINV
sta restore_irq._orig_irqvec
lda cx16.CINV+1
sta restore_irq._orig_irqvec+1
cli
rts
}}
}
asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda #0
adc #0
sta _use_kernal
sei
lda #<_irq_handler
sta cx16.CINV
lda #>_irq_handler
sta cx16.CINV+1
lda cx16.VERA_IEN
ora #%00000001 ; enable the vsync irq
sta cx16.VERA_IEN
cli
rts
_irq_handler jsr _irq_handler_init
_modified jsr $ffff ; modified
jsr _irq_handler_end
lda _use_kernal
bne +
; end irq processing - don't use kernal's irq handling
lda cx16.VERA_ISR
ora #1
sta cx16.VERA_ISR ; clear Vera Vsync irq status
ply
plx
pla
rti
+ jmp (restore_irq._orig_irqvec) ; continue with normal kernal irq routine
_use_kernal .byte 0
_irq_handler_init
; save all zp scratch registers and the X register as these might be clobbered by the irq routine
stx IRQ_X_REG
lda P8ZP_SCRATCH_B1
sta IRQ_SCRATCH_ZPB1
lda P8ZP_SCRATCH_REG
sta IRQ_SCRATCH_ZPREG
lda P8ZP_SCRATCH_W1
sta IRQ_SCRATCH_ZPWORD1
lda P8ZP_SCRATCH_W1+1
sta IRQ_SCRATCH_ZPWORD1+1
lda P8ZP_SCRATCH_W2
sta IRQ_SCRATCH_ZPWORD2
lda P8ZP_SCRATCH_W2+1
sta IRQ_SCRATCH_ZPWORD2+1
; stack protector; make sure we don't clobber the top of the evaluation stack
dex
dex
dex
dex
dex
dex
cld
rts
_irq_handler_end
; restore all zp scratch registers and the X register
lda IRQ_SCRATCH_ZPB1
sta P8ZP_SCRATCH_B1
lda IRQ_SCRATCH_ZPREG
sta P8ZP_SCRATCH_REG
lda IRQ_SCRATCH_ZPWORD1
sta P8ZP_SCRATCH_W1
lda IRQ_SCRATCH_ZPWORD1+1
sta P8ZP_SCRATCH_W1+1
lda IRQ_SCRATCH_ZPWORD2
sta P8ZP_SCRATCH_W2
lda IRQ_SCRATCH_ZPWORD2+1
sta P8ZP_SCRATCH_W2+1
ldx IRQ_X_REG
rts
IRQ_X_REG .byte 0
IRQ_SCRATCH_ZPB1 .byte 0
IRQ_SCRATCH_ZPREG .byte 0
IRQ_SCRATCH_ZPWORD1 .word 0
IRQ_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub restore_irq() clobbers(A) {
%asm {{
sei
lda _orig_irqvec
sta cx16.CINV
lda _orig_irqvec+1
sta cx16.CINV+1
lda cx16.VERA_IEN
and #%11110000 ; disable all Vera IRQs
ora #%00000001 ; enable only the vsync Irq
sta cx16.VERA_IEN
cli
rts
_orig_irqvec .word 0
}}
}
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda cx16.r0
ldy cx16.r0+1
sei
lda cx16.VERA_IEN
and #%11110000 ; clear other IRQs
ora #%00000010 ; enable the line (raster) irq
sta cx16.VERA_IEN
lda cx16.r0
ldy cx16.r0+1
jsr set_rasterline
lda #<_raster_irq_handler
sta cx16.CINV
lda #>_raster_irq_handler
sta cx16.CINV+1
cli
rts
_raster_irq_handler
jsr set_irq._irq_handler_init
_modified jsr $ffff ; modified
jsr set_irq._irq_handler_end
; end irq processing - don't use kernal's irq handling
lda cx16.VERA_ISR
ora #%00000010
sta cx16.VERA_ISR ; clear Vera line irq status
ply
plx
pla
rti
}}
}
asmsub set_rasterline(uword line @AY) {
%asm {{
sta cx16.VERA_IRQ_LINE_L
lda cx16.VERA_IEN
and #%01111111
sta cx16.VERA_IEN
tya
lsr a
ror a
and #%10000000
ora cx16.VERA_IEN
sta cx16.VERA_IEN
rts
}}
}
}
sys {
; ------- lowlevel system routines --------
const ubyte target = 16 ; compilation target specifier. 64 = C64, 16 = CommanderX16.
asmsub reset_system() {
; Soft-reset the system back to initial power-on Basic prompt.
%asm {{
sei
stz $01 ; bank the kernal in
jmp (cx16.RESET_VEC)
}}
}
asmsub wait(uword jiffies @AY) {
; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
; note: regular system vsync irq handler must be running, and no nother irqs
%asm {{
- wai ; wait for irq (assume it was vsync)
cmp #0
bne +
dey
+ dec a
bne -
cpy #0
bne -
rts
}}
}
inline asmsub waitvsync() {
; --- suspend execution until the next vsync has occurred, without depending on custom irq handling.
; note: system vsync irq handler has to be active for this routine to work (and no other irqs-- which is the default).
; note: a more accurate way to wait for vsync is to set up a vsync irq handler instead.
%asm {{
wai
}}
}
inline asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
%asm {{
sta cx16.r2
sty cx16.r2+1
jsr cx16.memory_copy
}}
}
inline asmsub memset(uword mem @R0, uword numbytes @R1, ubyte value @A) clobbers(A,X,Y) {
%asm {{
jsr cx16.memory_fill
}}
}
asmsub memsetw(uword mem @R0, uword numwords @R1, uword value @AY) clobbers (A,X,Y) {
%asm {{
ldx cx16.r0
stx P8ZP_SCRATCH_W1
ldx cx16.r0+1
stx P8ZP_SCRATCH_W1+1
ldx cx16.r1
stx P8ZP_SCRATCH_W2
ldx cx16.r1+1
stx P8ZP_SCRATCH_W2+1
jmp prog8_lib.memsetw
}}
}
inline asmsub rsave() {
; save cpu status flag and all registers A, X, Y.
; see http://6502.org/tutorials/register_preservation.html
%asm {{
php
pha
phy
phx
}}
}
inline asmsub rrestore() {
; restore all registers and cpu status flag
%asm {{
plx
ply
pla
plp
}}
}
inline asmsub read_flags() -> ubyte @A {
%asm {{
php
pla
}}
}
inline asmsub clear_carry() {
%asm {{
clc
}}
}
inline asmsub set_carry() {
%asm {{
sec
}}
}
inline asmsub clear_irqd() {
%asm {{
cli
}}
}
inline asmsub set_irqd() {
%asm {{
sei
}}
}
inline asmsub exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
%asm {{
jsr c64.CLRCHN ; reset i/o channels
ldx prog8_lib.orig_stackpointer
txs
rts ; return to original caller
}}
}
inline asmsub progend() -> uword @AY {
%asm {{
lda #<prog8_program_end
ldy #>prog8_program_end
}}
}
}

View File

@ -4,7 +4,6 @@
;
; indent format: TABS, size=8
%target cx16
%import syslib
%import conv
@ -15,8 +14,31 @@ const ubyte DEFAULT_WIDTH = 80
const ubyte DEFAULT_HEIGHT = 60
sub clear_screen() {
clear_screenchars(' ')
sub clear_screen() {
txt.chrout(147)
}
sub home() {
txt.chrout(19)
}
sub nl() {
txt.chrout('\n')
}
sub spc() {
txt.chrout(' ')
}
asmsub column(ubyte col @A) clobbers(A, X, Y) {
; ---- set the cursor on the given column (starting with 0) on the current line
%asm {{
sec
jsr c64.PLOT
tay
clc
jmp c64.PLOT
}}
}
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
@ -154,10 +176,9 @@ sub uppercase() {
cx16.screen_set_charset(2, 0) ; uppercase charset
}
asmsub scroll_left (ubyte dummy @ Pc) clobbers(A, Y) {
asmsub scroll_left() clobbers(A, Y) {
; ---- scroll the whole screen 1 character to the left
; contents of the rightmost column are unchanged, you should clear/refill this yourself
; Carry flag is a dummy on the cx16
%asm {{
phx
jsr c64.SCREEN
@ -198,10 +219,9 @@ _lx ldx #0 ; modified
}}
}
asmsub scroll_right (ubyte dummy @ Pc) clobbers(A) {
asmsub scroll_right() clobbers(A) {
; ---- scroll the whole screen 1 character to the right
; contents of the leftmost column are unchanged, you should clear/refill this yourself
; Carry flag is a dummy on the cx16
%asm {{
phx
jsr c64.SCREEN
@ -250,10 +270,9 @@ _lx ldx #0 ; modified
}}
}
asmsub scroll_up (ubyte dummy @ Pc) clobbers(A, Y) {
asmsub scroll_up() clobbers(A, Y) {
; ---- scroll the whole screen 1 character up
; contents of the bottom row are unchanged, you should refill/clear this yourself
; Carry flag is a dummy on the cx16
%asm {{
phx
jsr c64.SCREEN
@ -300,10 +319,9 @@ _nextline
}}
}
asmsub scroll_down (ubyte dummy @ Pc) clobbers(A, Y) {
asmsub scroll_down() clobbers(A, Y) {
; ---- scroll the whole screen 1 character down
; contents of the top row are unchanged, you should refill/clear this yourself
; Carry flag is a dummy on the cx16
%asm {{
phx
jsr c64.SCREEN
@ -401,7 +419,7 @@ _print_byte_digits
jsr c64.CHROUT
pla
jsr c64.CHROUT
jmp _ones
bra _ones
+ pla
cmp #'0'
beq _ones
@ -424,7 +442,7 @@ asmsub print_b (byte value @ A) clobbers(A,Y) {
jsr c64.CHROUT
+ pla
jsr conv.byte2decimal
jmp print_ub._print_byte_digits
bra print_ub._print_byte_digits
}}
}
@ -475,7 +493,7 @@ asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
jsr print_ubbin
pla
clc
jmp print_ubbin
bra print_ubbin
}}
}
@ -488,7 +506,7 @@ asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
jsr print_ubhex
pla
clc
jmp print_ubhex
bra print_ubhex
}}
}
@ -551,7 +569,7 @@ asmsub print_w (word value @ AY) clobbers(A,Y) {
adc #1
bcc +
iny
+ jmp print_uw
+ bra print_uw
}}
}
@ -579,95 +597,126 @@ asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A) {
; ---- sets the character in the screen matrix at the given position
%asm {{
pha
txa
asl a
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
pla
sta cx16.VERA_DATA0
rts
pha
txa
asl a
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
pla
sta cx16.VERA_DATA0
rts
}}
}
asmsub getchr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
; ---- get the character in the screen matrix at the given location
%asm {{
asl a
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda cx16.VERA_DATA0
rts
asl a
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda cx16.VERA_DATA0
rts
}}
}
asmsub setclr (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A) {
; ---- set the color in A on the screen matrix at the given position
; note: on the CommanderX16 this allows you to set both Fg and Bg colors;
; use the high nybble in A to set the Bg color!
%asm {{
pha
txa
asl a
ina
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
pla
sta cx16.VERA_DATA0
rts
pha
txa
asl a
ina
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
pla
sta cx16.VERA_DATA0
rts
}}
}
asmsub getclr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
; ---- get the color in the screen color matrix at the given location
%asm {{
asl a
ina
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda cx16.VERA_DATA0
rts
asl a
ina
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda cx16.VERA_DATA0
rts
}}
}
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
; ---- set char+color at the given position on the screen
; note: color handling is the same as on the C64: it only sets the foreground color.
; use setcc2 if you want Cx-16 specific feature of setting both Bg+Fg colors.
%asm {{
phx
lda column
asl a
tax
ldy row
lda charcolor
and #$0f
sta P8ZP_SCRATCH_B1
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda char
sta cx16.VERA_DATA0
inx
stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda cx16.VERA_DATA0
and #$f0
ora P8ZP_SCRATCH_B1
sta cx16.VERA_DATA0
plx
rts
phx
lda column
asl a
tax
ldy row
lda charcolor
and #$0f
sta P8ZP_SCRATCH_B1
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda char
sta cx16.VERA_DATA0
inx
stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda cx16.VERA_DATA0
and #$f0
ora P8ZP_SCRATCH_B1
sta cx16.VERA_DATA0
plx
rts
}}
}
sub setcc2 (ubyte column, ubyte row, ubyte char, ubyte colors) {
; ---- set char+color at the given position on the screen
; note: on the CommanderX16 this allows you to set both Fg and Bg colors;
; use the high nybble in A to set the Bg color!
%asm {{
phx
lda column
asl a
tax
ldy row
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda char
sta cx16.VERA_DATA0
inx
stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda colors
sta cx16.VERA_DATA0
plx
rts
}}
}
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
; ---- safe wrapper around PLOT kernel routine, to save the X register.
; ---- safe wrapper around PLOT kernal routine, to save the X register.
%asm {{
phx
tax

View File

@ -1,3 +1,5 @@
; routine to draw the Commander X16's log in petscii.
%import textio
cx16logo {
@ -14,7 +16,7 @@ cx16logo {
uword strptr
for strptr in logo_lines
txt.print(strptr)
txt.chrout('\n')
txt.nl()
}
str[] logo_lines = [

View File

@ -1,20 +1,22 @@
%import textio
%import syslib
; C64 and Cx16 disk drive I/O routines.
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
; Note: this code is compatible with C64 and CX16.
%import textio
%import string
%import syslib
diskio {
sub directory(ubyte drivenumber) -> byte {
; -- Shows the directory contents of disk drive 8-11 (provide as argument).
sub directory(ubyte drivenumber) -> ubyte {
; -- Prints the directory contents of disk drive 8-11 to the screen. Returns success.
c64.SETNAM(1, "$")
c64.SETLFS(1, drivenumber, 0)
void c64.OPEN() ; open 1,8,0,"$"
c64.SETLFS(13, drivenumber, 0)
void c64.OPEN() ; open 13,8,0,"$"
if_cs
goto io_error
void c64.CHKIN(1) ; use #1 as input channel
void c64.CHKIN(13) ; use #13 as input channel
if_cs
goto io_error
@ -25,31 +27,34 @@ diskio {
; while not key pressed / EOF encountered, read data.
ubyte status = c64.READST()
while not status {
txt.print_uw(mkword(c64.CHRIN(), c64.CHRIN()))
txt.chrout(' ')
ubyte low = c64.CHRIN()
ubyte high = c64.CHRIN()
txt.print_uw(mkword(high, low))
txt.spc()
ubyte @zp char
do {
repeat {
char = c64.CHRIN()
if char==0
break
txt.chrout(char)
} until char==0
txt.chrout('\n')
}
txt.nl()
void c64.CHRIN() ; skip 2 bytes
void c64.CHRIN()
status = c64.READST()
void c64.STOP()
if_nz
if c64.STOP2()
break
}
io_error:
status = c64.READST()
c64.CLRCHN() ; restore default i/o devices
c64.CLOSE(1)
c64.CLOSE(13)
if status and status != 64 { ; 64=end of file
if status and status & $40 == 0 { ; bit 6=end of file
txt.print("\ni/o error, status: ")
txt.print_ub(status)
txt.chrout('\n')
txt.nl()
return false
}
@ -57,9 +62,323 @@ io_error:
}
sub status(ubyte drivenumber) {
; -- display the disk drive's current status message
c64.SETNAM(0, $0000)
; internal variables for the iterative file lister / loader
ubyte list_skip_disk_name
uword list_pattern
uword list_blocks
ubyte iteration_in_progress = false
ubyte @zp first_byte
ubyte have_first_byte
str list_filename = "?" * 32
; ----- get a list of files (uses iteration functions internally) -----
sub list_files(ubyte drivenumber, uword pattern_ptr, uword name_ptrs, ubyte max_names) -> ubyte {
; -- fill the array 'name_ptrs' with (pointers to) the names of the files requested.
uword names_buffer = memory("filenames", 512)
uword buffer_start = names_buffer
ubyte files_found = 0
if lf_start_list(drivenumber, pattern_ptr) {
while lf_next_entry() {
@(name_ptrs) = lsb(names_buffer)
name_ptrs++
@(name_ptrs) = msb(names_buffer)
name_ptrs++
names_buffer += string.copy(diskio.list_filename, names_buffer) + 1
files_found++
if names_buffer - buffer_start > 512-18
break
if files_found == max_names
break
}
lf_end_list()
}
return files_found
}
; ----- iterative file lister functions (uses io channel 12) -----
sub lf_start_list(ubyte drivenumber, uword pattern_ptr) -> ubyte {
; -- start an iterative file listing with optional pattern matching.
; note: only a single iteration loop can be active at a time!
lf_end_list()
list_pattern = pattern_ptr
list_skip_disk_name = true
iteration_in_progress = true
c64.SETNAM(1, "$")
c64.SETLFS(12, drivenumber, 0)
void c64.OPEN() ; open 12,8,0,"$"
if_cs
goto io_error
void c64.CHKIN(12) ; use #12 as input channel
if_cs
goto io_error
repeat 4 {
void c64.CHRIN() ; skip the 4 prologue bytes
}
if c64.READST()==0
return true
io_error:
lf_end_list()
return false
}
sub lf_next_entry() -> ubyte {
; -- retrieve the next entry from an iterative file listing session.
; results will be found in list_blocks and list_filename.
; if it returns false though, there are no more entries (or an error occurred).
if not iteration_in_progress
return false
repeat {
void c64.CHKIN(12) ; use #12 as input channel again
uword nameptr = &list_filename
ubyte blocks_lsb = c64.CHRIN()
ubyte blocks_msb = c64.CHRIN()
if c64.READST()
goto close_end
list_blocks = mkword(blocks_msb, blocks_lsb)
; read until the filename starts after the first "
while c64.CHRIN()!='\"' {
if c64.READST()
goto close_end
}
; read the filename
repeat {
ubyte char = c64.CHRIN()
if char==0
break
if char=='\"'
break
@(nameptr) = char
nameptr++
}
@(nameptr) = 0
while c64.CHRIN() {
; read the rest of the entry until the end
}
void c64.CHRIN() ; skip 2 bytes
void c64.CHRIN()
if not list_skip_disk_name {
if not list_pattern
return true
if prog8_lib.pattern_match(list_filename, list_pattern)
return true
}
list_skip_disk_name = false
}
close_end:
lf_end_list()
return false
}
sub lf_end_list() {
; -- end an iterative file listing session (close channels).
if iteration_in_progress {
c64.CLRCHN()
c64.CLOSE(12)
iteration_in_progress = false
}
}
; ----- iterative file loader functions (uses io channel 11) -----
sub f_open(ubyte drivenumber, uword filenameptr) -> ubyte {
; -- open a file for iterative reading with f_read
; note: only a single iteration loop can be active at a time!
f_close()
c64.SETNAM(string.length(filenameptr), filenameptr)
c64.SETLFS(11, drivenumber, 0)
void c64.OPEN() ; open 11,8,0,"filename"
if_cc {
iteration_in_progress = true
have_first_byte = false
void c64.CHKIN(11) ; use #11 as input channel
if_cc {
first_byte = c64.CHRIN() ; read first byte to test for file not found
if not c64.READST() {
have_first_byte = true
return true
}
}
}
f_close()
return false
}
sub f_read(uword bufferpointer, uword num_bytes) -> uword {
; -- read from the currently open file, up to the given number of bytes.
; returns the actual number of bytes read. (checks for End-of-file and error conditions)
if not iteration_in_progress or not num_bytes
return 0
list_blocks = 0 ; we reuse this variable for the total number of bytes read
if have_first_byte {
have_first_byte=false
@(bufferpointer) = first_byte
bufferpointer++
list_blocks++
num_bytes--
}
void c64.CHKIN(11) ; use #11 as input channel again
%asm {{
lda bufferpointer
sta _in_buffer+1
lda bufferpointer+1
sta _in_buffer+2
}}
repeat num_bytes {
%asm {{
jsr c64.CHRIN
sta cx16.r5
_in_buffer sta $ffff
inc _in_buffer+1
bne +
inc _in_buffer+2
+ inc list_blocks
bne +
inc list_blocks+1
+
}}
if cx16.r5==$0d { ; chance on I/o error status?
first_byte = c64.READST()
if first_byte & $40
f_close() ; end of file, close it
if first_byte
return list_blocks
}
}
return list_blocks
}
sub f_read_all(uword bufferpointer) -> uword {
; -- read the full contents of the file, returns number of bytes read.
if not iteration_in_progress
return 0
list_blocks = 0 ; we reuse this variable for the total number of bytes read
if have_first_byte {
have_first_byte=false
@(bufferpointer) = first_byte
bufferpointer++
list_blocks++
}
while not c64.READST() {
list_blocks += f_read(bufferpointer, 256)
bufferpointer += 256
}
return list_blocks
}
asmsub f_readline(uword bufptr @AY) clobbers(X) -> ubyte @Y {
; Routine to read text lines from a text file. Lines must be less than 255 characters.
; Reads characters from the input file UNTIL a newline or return character (or EOF).
; The line read will be 0-terminated in the buffer (and not contain the end of line character).
; The length of the line is returned in Y. Note that an empty line is okay and is length 0!
; I/O error status should be checked by the caller itself via READST() routine.
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldx #11
jsr c64.CHKIN ; use channel 11 again for input
ldy #0
lda have_first_byte
beq _loop
lda #0
sta have_first_byte
lda first_byte
sta (P8ZP_SCRATCH_W1),y
iny
_loop jsr c64.CHRIN
sta (P8ZP_SCRATCH_W1),y
beq _end
iny
cmp #$0a
beq _line_end
cmp #$0d
bne _loop
_line_end dey ; get rid of the trailing end-of-line char
lda #0
sta (P8ZP_SCRATCH_W1),y
_end rts
}}
}
sub f_close() {
; -- end an iterative file loading session (close channels).
if iteration_in_progress {
c64.CLRCHN()
c64.CLOSE(11)
iteration_in_progress = false
}
}
; ----- iterative file saver functions (uses io channel 14) -----
sub f_open_w(ubyte drivenumber, uword filenameptr) -> ubyte {
; -- open a file for iterative writing with f_write
f_close_w()
c64.SETNAM(string.length(filenameptr), filenameptr)
c64.SETLFS(14, drivenumber, 1)
void c64.OPEN() ; open 14,8,1,"filename"
if_cc {
void c64.CHKOUT(14) ; use #14 as input channel
return not c64.READST()
}
f_close_w()
return false
}
sub f_write(uword bufferpointer, uword num_bytes) -> ubyte {
; -- write the given umber of bytes to the currently open file
if num_bytes!=0 {
void c64.CHKOUT(14) ; use #14 as input channel again
repeat num_bytes {
c64.CHROUT(@(bufferpointer))
bufferpointer++
}
return not c64.READST()
}
return true
}
sub f_close_w() {
; -- end an iterative file writing session (close channels).
c64.CLRCHN()
c64.CLOSE(14)
}
; ---- other functions ----
sub status(ubyte drivenumber) -> uword {
; -- retrieve the disk drive's current status message
uword messageptr = &filename
c64.SETNAM(0, filename)
c64.SETLFS(15, drivenumber, 15)
void c64.OPEN() ; open 15,8,15
if_cs
@ -68,19 +387,27 @@ io_error:
if_cs
goto io_error
while not c64.READST()
txt.chrout(c64.CHRIN())
while not c64.READST() {
@(messageptr) = c64.CHRIN()
messageptr++
}
io_error:
@(messageptr) = 0
done:
c64.CLRCHN() ; restore default i/o devices
c64.CLOSE(15)
return filename
io_error:
filename = "?disk error"
goto done
}
sub save(ubyte drivenumber, uword filenameptr, uword address, uword size) -> byte {
c64.SETNAM(strlen(filenameptr), filenameptr)
sub save(ubyte drivenumber, uword filenameptr, uword address, uword size) -> ubyte {
c64.SETNAM(string.length(filenameptr), filenameptr)
c64.SETLFS(1, drivenumber, 0)
uword end_address = address + size
first_byte = 0 ; result var reuse
%asm {{
lda address
@ -98,13 +425,16 @@ io_error:
}}
if_cc
return c64.READST()==0
first_byte = c64.READST()==0
return false
c64.CLRCHN()
c64.CLOSE(1)
return first_byte
}
sub load(ubyte drivenumber, uword filenameptr, uword address_override) -> uword {
c64.SETNAM(strlen(filenameptr), filenameptr)
c64.SETNAM(string.length(filenameptr), filenameptr)
ubyte secondary = 1
uword end_of_load = 0
if address_override
@ -122,6 +452,9 @@ io_error:
+ ldx P8ZP_SCRATCH_REG
}}
c64.CLRCHN()
c64.CLOSE(1)
if end_of_load
return end_of_load - address_override
@ -133,10 +466,9 @@ io_error:
sub delete(ubyte drivenumber, uword filenameptr) {
; -- delete a file on the drive
ubyte flen = strlen(filenameptr)
filename[0] = 's'
filename[1] = ':'
memcopy(filenameptr, &filename+2, flen+1)
ubyte flen = string.copy(filenameptr, &filename+2)
c64.SETNAM(flen+2, filename)
c64.SETLFS(1, drivenumber, 15)
void c64.OPEN()
@ -146,13 +478,11 @@ io_error:
sub rename(ubyte drivenumber, uword oldfileptr, uword newfileptr) {
; -- rename a file on the drive
ubyte flen_old = strlen(oldfileptr)
ubyte flen_new = strlen(newfileptr)
filename[0] = 'r'
filename[1] = ':'
memcopy(newfileptr, &filename+2, flen_new)
ubyte flen_new = string.copy(newfileptr, &filename+2)
filename[flen_new+2] = '='
memcopy(oldfileptr, &filename+3+flen_new, flen_old+1)
ubyte flen_old = string.copy(oldfileptr, &filename+3+flen_new)
c64.SETNAM(3+flen_new+flen_old, filename)
c64.SETLFS(1, drivenumber, 15)
void c64.OPEN()

View File

@ -1,11 +1,8 @@
; Prog8 internal Math library routines - always included by the compiler
; Internal Math library routines - always included by the compiler
; Generic machine independent 6502 code.
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
;
; indent format: TABS, size=8
; some more interesting routines can be found here:
; http://6502org.wikidot.com/software-math
; http://codebase64.org/doku.php?id=base:6502_6510_maths
@ -247,68 +244,45 @@ randseed .proc
.pend
randbyte .proc
; -- 8-bit pseudo random number generator into A
lda _seed
beq _eor
asl a
beq _done ; if the input was $80, skip the EOR
bcc _done
_eor eor #$1d ; xor with magic value see below for possible values
_done sta _seed
rts
_seed .byte $3a
; possible 'magic' eor bytes are:
; $1d, $2b, $2d, $4d, $5f, $63, $65, $69
; $71, $87, $8d, $a9, $c3, $cf, $e7, $f5
randbyte .proc
; -- 8 bit pseudo random number generator into A (by just reusing randword)
jmp randword
.pend
randword .proc
; -- 16 bit pseudo random number generator into AY
magic_eor = $3f1d
; possible magic eor words are:
; $3f1d, $3f81, $3fa5, $3fc5, $4075, $409d, $40cd, $4109
; $413f, $414b, $4153, $4159, $4193, $4199, $41af, $41bb
; rand64k ;Factors of 65535: 3 5 17 257
lda sr1+1
asl a
asl a
eor sr1+1
asl a
eor sr1+1
asl a
asl a
eor sr1+1
asl a
rol sr1 ;shift this left, "random" bit comes from low
rol sr1+1
; rand32k ;Factors of 32767: 7 31 151 are independent and can be combined
lda sr2+1
asl a
eor sr2+1
asl a
asl a
ror sr2 ;shift this right, random bit comes from high - nicer when eor with sr1
rol sr2+1
lda sr1+1 ;can be left out
eor sr2+1 ;if you dont use
tay ;y as suggested
lda sr1 ;mix up lowbytes of SR1
eor sr2 ;and SR2 to combine both
rts
lda _seed
beq _lowZero ; $0000 and $8000 are special values to test for
sr1 .word $a55a
sr2 .word $7653
; Do a normal shift
asl _seed
lda _seed+1
rol a
bcc _noEor
_doEor ; high byte is in A
eor #>magic_eor
sta _seed+1
lda _seed
eor #<magic_eor
sta _seed
ldy _seed+1
rts
_lowZero lda _seed+1
beq _doEor ; High byte is also zero, so apply the EOR
; For speed, you could store 'magic' into 'seed' directly
; instead of running the EORs
; wasn't zero, check for $8000
asl a
beq _noEor ; if $00 is left after the shift, then it was $80
bcs _doEor ; else, do the EOR based on the carry bit as usual
_noEor sta _seed+1
tay
lda _seed
rts
_seed .word $2c9e
.pend
@ -774,6 +748,38 @@ stack_mul_word_100 .proc
rts
.pend
stack_mul_word_320 .proc
; stackW = stackLo * 256 + stackLo * 64 (stackHi doesn't matter)
ldy P8ESTACK_LO+1,x
lda #0
sta P8ESTACK_HI+1,x
tya
asl a
rol P8ESTACK_HI+1,x
asl a
rol P8ESTACK_HI+1,x
asl a
rol P8ESTACK_HI+1,x
asl a
rol P8ESTACK_HI+1,x
asl a
rol P8ESTACK_HI+1,x
asl a
rol P8ESTACK_HI+1,x
sta P8ESTACK_LO+1,x
tya
clc
adc P8ESTACK_HI+1,x
sta P8ESTACK_HI+1,x
rts
.pend
stack_mul_word_640 .proc
; stackW = (stackLo * 2 * 320) (stackHi doesn't matter)
asl P8ESTACK_LO+1,x
jmp stack_mul_word_320
.pend
; ----------- optimized multiplications (in-place A (byte) and ?? (word)) : ---------
mul_byte_3 .proc
@ -1178,8 +1184,6 @@ mul_word_40 .proc
rol a
asl P8ZP_SCRATCH_W1
rol a
asl P8ZP_SCRATCH_W1
rol a
tay
lda P8ZP_SCRATCH_W1
rts
@ -1241,6 +1245,39 @@ mul_word_100 .proc
rts
.pend
mul_word_320 .proc
; AY = A * 256 + A * 64 (msb in Y doesn't matter)
sta P8ZP_SCRATCH_B1
ldy #0
sty P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
pha
clc
lda P8ZP_SCRATCH_B1
adc P8ZP_SCRATCH_REG
tay
pla
rts
.pend
mul_word_640 .proc
; AY = (A * 2 * 320) (msb in Y doesn't matter)
asl a
jmp mul_word_320
.pend
; ----------- end optimized multiplications -----------
@ -1301,6 +1338,33 @@ shift_left_w_3 .proc
jmp shift_left_w_7._shift3
.pend
shift_left_w .proc
; -- variable number of shifts left
inx
ldy P8ESTACK_LO,x
bne _shift
rts
_shift asl P8ESTACK_LO+1,x
rol P8ESTACK_HI+1,x
dey
bne _shift
rts
.pend
shift_right_uw .proc
; -- uword variable number of shifts right
inx
ldy P8ESTACK_LO,x
bne _shift
rts
_shift lsr P8ESTACK_HI+1,x
ror P8ESTACK_LO+1,x
dey
bne _shift
rts
.pend
shift_right_uw_7 .proc
lda P8ESTACK_LO+1,x
sta P8ZP_SCRATCH_B1
@ -1431,6 +1495,21 @@ shift_right_w_3 .proc
.pend
shift_right_w .proc
; -- signed word variable number of shifts right
inx
ldy P8ESTACK_LO,x
bne _shift
rts
_shift lda P8ESTACK_HI+1,x
asl a
ror P8ESTACK_HI+1,x
ror P8ESTACK_LO+1,x
dey
bne _shift
rts
.pend
; support for bit shifting that is too large to be unrolled:
@ -1449,3 +1528,71 @@ _negative lsr a
rts
.pend
square .proc
; -- calculate square root of signed word in AY, result in AY
; routine by Lee Davsion, source: http://6502.org/source/integers/square.htm
; using this routine is about twice as fast as doing a regular multiplication.
;
; Calculates the 16 bit unsigned integer square of the signed 16 bit integer in
; Numberl/Numberh. The result is always in the range 0 to 65025 and is held in
; Squarel/Squareh
;
; The maximum input range is only +/-255 and no checking is done to ensure that
; this is so.
;
; This routine is useful if you are trying to draw circles as for any circle
;
; x^2+y^2=r^2 where x and y are the co-ordinates of any point on the circle and
; r is the circle radius
numberl = P8ZP_SCRATCH_W1 ; number to square low byte
numberh = P8ZP_SCRATCH_W1+1 ; number to square high byte
squarel = P8ZP_SCRATCH_W2 ; square low byte
squareh = P8ZP_SCRATCH_W2+1 ; square high byte
tempsq = P8ZP_SCRATCH_B1 ; temp byte for intermediate result
sta numberl
sty numberh
stx P8ZP_SCRATCH_REG
lda #$00 ; clear a
sta squarel ; clear square low byte
; (no need to clear the high byte, it gets shifted out)
lda numberl ; get number low byte
ldx numberh ; get number high byte
bpl _nonneg ; if +ve don't negate it
; else do a two's complement
eor #$ff ; invert
sec ; +1
adc #$00 ; and add it
_nonneg:
sta tempsq ; save abs(number)
ldx #$08 ; set bit count
_nextr2bit:
asl squarel ; low byte *2
rol squareh ; high byte *2+carry from low
asl a ; shift number byte
bcc _nosqadd ; don't do add if c = 0
tay ; save a
clc ; clear carry for add
lda tempsq ; get number
adc squarel ; add number^2 low byte
sta squarel ; save number^2 low byte
lda #$00 ; clear a
adc squareh ; add number^2 high byte
sta squareh ; save number^2 high byte
tya ; get a back
_nosqadd:
dex ; decrement bit count
bne _nextr2bit ; go do next bit
lda squarel
ldy squareh
ldx P8ZP_SCRATCH_REG
rts
.pend

View File

@ -1,9 +1,7 @@
; Prog8 internal Math library routines - always included by the compiler
; Internal Math library routines - always included by the compiler
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
;
; indent format: TABS, size=8
math {
%asminclude "library:math.asm", ""
%asminclude "library:math.asm"
}

View File

@ -432,6 +432,7 @@ func_min_ub_stack .proc
func_min_b_into_A .proc
; -- min(barray) -> A. (array in P8ZP_SCRATCH_W1, num elements in A)
tay
dey
lda #127
sta P8ZP_SCRATCH_B1
- lda (P8ZP_SCRATCH_W1),y
@ -548,6 +549,7 @@ func_min_w_stack .proc
func_max_ub_into_A .proc
; -- max(ubarray) -> A (array in P8ZP_SCRATCH_W1, num elements in A)
tay
dey
lda #0
sta P8ZP_SCRATCH_B1
- lda (P8ZP_SCRATCH_W1),y
@ -1078,271 +1080,28 @@ _loop_hi ldy _index_first
.pend
func_exit .proc
; -- immediately exit the program with a return code in the A register
ldx orig_stackpointer
txs
rts ; return to original caller
.pend
func_peekw .proc
; -- read the word value on the address in AY
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
tay
pla
rts
.pend
func_read_flags_stack .proc
; -- put the processor status register on the stack
php
pla
sta P8ESTACK_LO,x
dex
rts
.pend
func_memset .proc
; note: clobbers A,Y
txa
pha
lda _arg_address
sta P8ZP_SCRATCH_W1
lda _arg_address+1
sta P8ZP_SCRATCH_W1+1
ldx _arg_numbytes
ldy _arg_numbytes+1
lda _arg_bytevalue
jsr memset
pla
tax
rts
_arg_address .word 0
_arg_numbytes .word 0
_arg_bytevalue .byte 0
.pend
func_memsetw .proc
; note: clobbers A,Y
txa
pha
lda _arg_address
sta P8ZP_SCRATCH_W1
lda _arg_address+1
sta P8ZP_SCRATCH_W1+1
lda _arg_numwords
sta P8ZP_SCRATCH_W2
lda _arg_numwords+1
sta P8ZP_SCRATCH_W2+1
lda _arg_wordvalue
ldy _arg_wordvalue+1
jsr memsetw
pla
tax
rts
_arg_address .word 0
_arg_numwords .word 0
_arg_wordvalue .word 0
.pend
func_memcopy .proc
; memcopy of any number of bytes, note: clobbers A,Y
stx P8ZP_SCRATCH_REG
lda _arg_from
sta P8ZP_SCRATCH_W1
lda _arg_from+1
sta P8ZP_SCRATCH_W1+1
lda _arg_to
sta P8ZP_SCRATCH_W2
lda _arg_to+1
sta P8ZP_SCRATCH_W2+1
ldy #0
ldx _arg_numbytes+1
beq _remain
- lda (P8ZP_SCRATCH_W1),y ; move a page at a time
sta (P8ZP_SCRATCH_W2),y
iny
bne -
inc P8ZP_SCRATCH_W1+1
inc P8ZP_SCRATCH_W2+1
dex
bne -
_remain ldx _arg_numbytes
beq _done
- lda (P8ZP_SCRATCH_W1),y ; move the remaining bytes
sta (P8ZP_SCRATCH_W2),y
iny
dex
bne -
_done ldx P8ZP_SCRATCH_REG
rts
_arg_from .word 0
_arg_to .word 0
_arg_numbytes .word 0
.pend
func_memcopy255 .proc
; fast memcopy of up to 255 bytes, note: clobbers A,Y
; note: also uses the _arg variables from regular func_memcopy
stx P8ZP_SCRATCH_REG
lda func_memcopy._arg_from
sta P8ZP_SCRATCH_W1
lda func_memcopy._arg_from+1
sta P8ZP_SCRATCH_W1+1
lda func_memcopy._arg_to
sta P8ZP_SCRATCH_W2
lda func_memcopy._arg_to+1
sta P8ZP_SCRATCH_W2+1
ldx func_memcopy._arg_numbytes
ldy #0
- lda (P8ZP_SCRATCH_W1), y
sta (P8ZP_SCRATCH_W2), y
iny
dex
bne -
ldx P8ZP_SCRATCH_REG
rts
.pend
func_leftstr .proc
; leftstr(source, target, length)
lda _arg_source
ldy _arg_source+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda _arg_target
ldy _arg_target+1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy _arg_length
lda #0
sta (P8ZP_SCRATCH_W2),y
cpy #0
bne _loop
rts
_loop dey
lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
cpy #0
bne _loop
+ rts
_arg_source .word 0
_arg_target .word 0
_arg_length .byte 0
.pend
func_rightstr .proc
; rightstr(source, target, length)
lda _arg_source
ldy _arg_source+1
jsr func_strlen_into_A
sec
sbc _arg_length
sta P8ZP_SCRATCH_B1
lda _arg_source
ldy _arg_source+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda _arg_target
ldy _arg_target+1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sty P8ZP_SCRATCH_REG
- ldy P8ZP_SCRATCH_B1
lda (P8ZP_SCRATCH_W1),y
inc P8ZP_SCRATCH_B1
ldy P8ZP_SCRATCH_REG
sta (P8ZP_SCRATCH_W2),y
inc P8ZP_SCRATCH_REG
cmp #0
bne -
rts
_arg_source .word 0
_arg_target .word 0
_arg_length .byte 0
.pend
func_strlen_into_A .proc
; -- put length of 0-terminated string in A/Y into A
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq +
iny
bne -
+ tya
rts
.pend
func_strlen_stack .proc
jsr func_strlen_into_A
sta P8ESTACK_LO,x
dex
rts
.pend
func_substr .proc
; substr(source, target, start, length)
lda _arg_source
ldy _arg_source+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda _arg_target
ldy _arg_target+1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy _arg_length
lda _arg_start
sta P8ZP_SCRATCH_B1
; adjust src location
clc
lda P8ZP_SCRATCH_W1
adc P8ZP_SCRATCH_B1
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ lda #0
sta (P8ZP_SCRATCH_W2),y
jmp _startloop
- lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
_startloop dey
cpy #$ff
bne -
rts
_arg_source .word 0
_arg_target .word 0
_arg_start .byte 0
_arg_length .byte 0
.pend
func_strcmp .proc
; -- compare 2 strings -> A
lda _arg_s2
ldy _arg_s2+1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda _arg_s1
ldy _arg_s1+1
jmp strcmp_mem
_arg_s1 .word 0
_arg_s2 .word 0
.pend
func_strcmp_stack .proc
jsr func_strcmp
sta P8ESTACK_LO,x
dex
rts
.pend
func_pokew .proc
; -- store the word value in AY in the address in P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_REG
ldy #0
sta (P8ZP_SCRATCH_W1),y
iny
lda P8ZP_SCRATCH_REG
sta (P8ZP_SCRATCH_W1),y
rts
.pend

View File

@ -1,9 +1,7 @@
; Prog8 internal library routines - always included by the compiler
; Internal library routines - always included by the compiler
; Generic machine independent 6502 code.
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
;
; indent format: TABS, size=8
read_byte_from_address_on_stack .proc
@ -993,6 +991,7 @@ _arg_index .byte 0
strcpy .proc
; copy a string (must be 0-terminated) from A/Y to (P8ZP_SCRATCH_W1)
; it is assumed the target string is large enough.
; returns the length of the string that was copied in Y.
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #$ff
@ -1020,33 +1019,33 @@ _arg_s2 .word 0
strcmp_mem .proc
; -- compares strings in s1 (AY) and s2 (P8ZP_SCRATCH_W2).
; Returns -1,0,1 in A, depeding on the ordering. Clobbers Y.
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop ldy #0
lda (P8ZP_SCRATCH_W1),y
bne +
lda (P8ZP_SCRATCH_W2),y
bne _return_minusone
beq _return
+ lda (P8ZP_SCRATCH_W2),y
sec
sbc (P8ZP_SCRATCH_W1),y
bmi _return_one
bne _return_minusone
inc P8ZP_SCRATCH_W1
bne +
inc P8ZP_SCRATCH_W1+1
+ inc P8ZP_SCRATCH_W2
bne _loop
inc P8ZP_SCRATCH_W2+1
bne _loop
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop ldy #0
lda (P8ZP_SCRATCH_W1),y
bne +
lda (P8ZP_SCRATCH_W2),y
bne _return_minusone
beq _return
+ lda (P8ZP_SCRATCH_W2),y
sec
sbc (P8ZP_SCRATCH_W1),y
bmi _return_one
bne _return_minusone
inc P8ZP_SCRATCH_W1
bne +
inc P8ZP_SCRATCH_W1+1
+ inc P8ZP_SCRATCH_W2
bne _loop
inc P8ZP_SCRATCH_W2+1
bne _loop
_return_one
lda #1
_return rts
lda #1
_return rts
_return_minusone
lda #-1
rts
.pend
lda #-1
rts
.pend
sign_extend_stack_byte .proc
@ -1072,3 +1071,15 @@ sign_extend_AY_byte .proc
pla
rts
.pend
strlen .proc
; -- returns the number of bytes in the string in AY, in Y.
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq +
iny
bne -
+ rts
.pend

View File

@ -1,10 +1,84 @@
; Prog8 internal library routines - always included by the compiler
; Internal library routines - always included by the compiler
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
;
; indent format: TABS, size=8
prog8_lib {
%asminclude "library:prog8_lib.asm", ""
%asminclude "library:prog8_funcs.asm", ""
%asminclude "library:prog8_lib.asm"
%asminclude "library:prog8_funcs.asm"
uword @zp retval_interm_uw ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
word @zp retval_interm_w ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
ubyte @zp retval_interm_ub ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
byte @zp retval_interm_b ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> ubyte @A {
%asm {{
; pattern matching of a string.
; Input: cx16.r0: A NUL-terminated, <255-length pattern
; AY: A NUL-terminated, <255-length string
;
; Output: A = 1 if the string matches the pattern, A = 0 if not.
;
; Notes: Clobbers A, X, Y. Each * in the pattern uses 4 bytes of stack.
;
; see http://6502.org/source/strings/patmatch.htm
str = P8ZP_SCRATCH_W1
stx P8ZP_SCRATCH_REG
sta str
sty str+1
lda cx16.r0
sta modify_pattern1+1
sta modify_pattern2+1
lda cx16.r0+1
sta modify_pattern1+2
sta modify_pattern2+2
jsr _match
lda #0
adc #0
ldx P8ZP_SCRATCH_REG
rts
_match
ldx #$00 ; x is an index in the pattern
ldy #$ff ; y is an index in the string
modify_pattern1
next lda $ffff,x ; look at next pattern character MODIFIED
cmp #'*' ; is it a star?
beq star ; yes, do the complicated stuff
iny ; no, let's look at the string
cmp #'?' ; is the pattern caracter a ques?
bne reg ; no, it's a regular character
lda (str),y ; yes, so it will match anything
beq fail ; except the end of string
reg cmp (str),y ; are both characters the same?
bne fail ; no, so no match
inx ; yes, keep checking
cmp #0 ; are we at end of string?
bne next ; not yet, loop
found rts ; success, return with c=1
star inx ; skip star in pattern
modify_pattern2
cmp $ffff,x ; string of stars equals one star MODIFIED
beq star ; so skip them also
stloop txa ; we first try to match with * = ""
pha ; and grow it by 1 character every
tya ; time we loop
pha ; save x and y on stack
jsr next ; recursive call
pla ; restore x and y
tay
pla
tax
bcs found ; we found a match, return with c=1
iny ; no match yet, try to grow * string
lda (str),y ; are we at the end of string?
bne stloop ; not yet, add a character
fail clc ; yes, no match found, return with c=0
rts
}}
}
}

View File

@ -0,0 +1,235 @@
; 0-terminated string manipulation routines.
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
string {
asmsub length(uword string @AY) clobbers(A) -> ubyte @Y {
; Returns the number of bytes in the string.
; This value is determined during runtime and counts upto the first terminating 0 byte in the string,
; regardless of the size of the string during compilation time. Dont confuse this with len and sizeof!
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq +
iny
bne -
+ rts
}}
}
asmsub left(uword source @R0, ubyte length @A, uword target @R1) clobbers(A, Y) {
; Copies the left side of the source string of the given length to target string.
; It is assumed the target string buffer is large enough to contain the result.
; Also, you have to make sure yourself that length is smaller or equal to the length of the source string.
; Modifies in-place, doesnt return a value (so cant be used in an expression).
%asm {{
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
ldy cx16.r0
sty P8ZP_SCRATCH_W1
ldy cx16.r0+1
sty P8ZP_SCRATCH_W1+1
ldy cx16.r1
sty P8ZP_SCRATCH_W2
ldy cx16.r1+1
sty P8ZP_SCRATCH_W2+1
tay
lda #0
sta (P8ZP_SCRATCH_W2),y
cpy #0
bne _loop
rts
_loop dey
lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
cpy #0
bne _loop
+ rts
}}
; asmgen.out(" jsr prog8_lib.func_leftstr")
}
asmsub right(uword source @R0, ubyte length @A, uword target @R1) clobbers(A,Y) {
; Copies the right side of the source string of the given length to target string.
; It is assumed the target string buffer is large enough to contain the result.
; Also, you have to make sure yourself that length is smaller or equal to the length of the source string.
; Modifies in-place, doesnt return a value (so cant be used in an expression).
%asm {{
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
sta P8ZP_SCRATCH_B1
lda cx16.r0
ldy cx16.r0+1
jsr string.length
tya
sec
sbc P8ZP_SCRATCH_B1
clc
adc cx16.r0
sta P8ZP_SCRATCH_W1
lda cx16.r0+1
adc #0
sta P8ZP_SCRATCH_W1+1
ldy cx16.r1
sty P8ZP_SCRATCH_W2
ldy cx16.r1+1
sty P8ZP_SCRATCH_W2+1
ldy P8ZP_SCRATCH_B1
lda #0
sta (P8ZP_SCRATCH_W2),y
cpy #0
bne _loop
rts
_loop dey
lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
cpy #0
bne _loop
+ rts
}}
}
asmsub slice(uword source @R0, ubyte start @A, ubyte length @Y, uword target @R1) clobbers(A, Y) {
; Copies a segment from the source string, starting at the given index,
; and of the given length to target string.
; It is assumed the target string buffer is large enough to contain the result.
; Also, you have to make sure yourself that start and length are within bounds of the strings.
; Modifies in-place, doesnt return a value (so cant be used in an expression).
%asm {{
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
; substr(source, target, start, length)
sta P8ZP_SCRATCH_B1
lda cx16.r0
sta P8ZP_SCRATCH_W1
lda cx16.r0+1
sta P8ZP_SCRATCH_W1+1
lda cx16.r1
sta P8ZP_SCRATCH_W2
lda cx16.r1+1
sta P8ZP_SCRATCH_W2+1
; adjust src location
clc
lda P8ZP_SCRATCH_W1
adc P8ZP_SCRATCH_B1
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ lda #0
sta (P8ZP_SCRATCH_W2),y
beq _startloop
- lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
_startloop dey
cpy #$ff
bne -
rts
}}
}
asmsub find(uword string @R0, ubyte character @A) -> uword @AY {
; Locates the first position of the given character in the string,
; returns the string starting with this character or $0000 if the character is not found.
%asm {{
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
sta P8ZP_SCRATCH_B1
lda cx16.r0
ldy cx16.r0+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq _notfound
cmp P8ZP_SCRATCH_B1
beq _found
iny
bne -
_notfound lda #0
ldy #0
rts
_found sty P8ZP_SCRATCH_B1
ldy P8ZP_SCRATCH_W1+1
lda P8ZP_SCRATCH_W1
clc
adc P8ZP_SCRATCH_B1
bcc +
iny
+ rts
}}
}
asmsub copy(uword source @R0, uword target @AY) clobbers(A) -> ubyte @Y {
; Copy a string to another, overwriting that one.
; Returns the length of the string that was copied.
; Often you dont have to call this explicitly and can just write string1 = string2
; but this function is useful if youre dealing with addresses for instance.
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda cx16.r0
ldy cx16.r0+1
jmp prog8_lib.strcpy
}}
}
asmsub compare(uword string1 @R0, uword string2 @AY) clobbers(Y) -> byte @A {
; Compares two strings for sorting.
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
; Note that you can also directly compare strings and string values with eachother using
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
%asm {{
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda cx16.r0
ldy cx16.r0+1
jmp prog8_lib.strcmp_mem
}}
}
asmsub lower(uword st @AY) -> ubyte @Y {
; Lowercases the petscii string in-place. Returns length of the string.
; (for efficiency, non-letter characters > 128 will also not be left intact,
; but regular text doesn't usually contain those characters anyway.)
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq _done
and #$7f
cmp #97
bcc +
cmp #123
bcs +
and #%11011111
+ sta (P8ZP_SCRATCH_W1),y
iny
bne -
_done rts
}}
}
asmsub upper(uword st @AY) -> ubyte @Y {
; Uppercases the petscii string in-place. Returns length of the string.
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq _done
cmp #65
bcc +
cmp #91
bcs +
ora #%00100000
+ sta (P8ZP_SCRATCH_W1),y
iny
bne -
_done rts
}}
}
}

View File

@ -1,48 +1,50 @@
; utility debug code to print the X (evalstack) and SP (cpu stack) registers.
%import textio
test_stack {
asmsub test() {
%asm {{
stx _saveX
lda #13
jsr txt.chrout
lda #'-'
ldy #12
- jsr txt.chrout
dey
bne -
lda #13
jsr txt.chrout
lda #'x'
jsr txt.chrout
lda #'='
jsr txt.chrout
lda _saveX
jsr txt.print_ub
lda #' '
jsr txt.chrout
lda #'s'
jsr txt.chrout
lda #'p'
jsr txt.chrout
lda #'='
jsr txt.chrout
tsx
txa
jsr txt.print_ub
lda #13
jsr txt.chrout
lda #'-'
ldy #12
- jsr txt.chrout
dey
bne -
lda #13
jsr txt.chrout
ldx _saveX
rts
_saveX .byte 0
stx _saveX
lda #13
jsr txt.chrout
lda #'-'
ldy #12
- jsr txt.chrout
dey
bne -
lda #13
jsr txt.chrout
lda #'x'
jsr txt.chrout
lda #'='
jsr txt.chrout
lda _saveX
jsr txt.print_ub
lda #' '
jsr txt.chrout
lda #'s'
jsr txt.chrout
lda #'p'
jsr txt.chrout
lda #'='
jsr txt.chrout
tsx
txa
jsr txt.print_ub
lda #13
jsr txt.chrout
lda #'-'
ldy #12
- jsr txt.chrout
dey
bne -
lda #13
jsr txt.chrout
ldx _saveX
rts
_saveX .byte 0
}}
}
}

View File

@ -1 +1 @@
5.2
7.1

View File

@ -6,8 +6,8 @@ import prog8.compiler.CompilationResult
import prog8.compiler.compileProgram
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.CompilationTarget
import prog8.parser.ParsingFailedError
import java.io.File
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.StandardWatchEventKinds
@ -16,63 +16,76 @@ import kotlin.system.exitProcess
fun main(args: Array<String>) {
printSoftwareHeader("compiler")
compileMain(args)
}
internal fun printSoftwareHeader(what: String) {
val buildVersion = object {}.javaClass.getResource("/version.txt").readText().trim()
println("\nProg8 $what v$buildVersion by Irmen de Jong (irmen@razorvine.net)")
println("\nProg8 compiler v$buildVersion by Irmen de Jong (irmen@razorvine.net)")
println("This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html\n")
val succes = compileMain(args)
if(!succes)
exitProcess(1)
}
fun pathFrom(stringPath: String, vararg rest: String): Path = FileSystems.getDefault().getPath(stringPath, *rest)
private fun compileMain(args: Array<String>) {
val cli = CommandLineInterface("prog8compiler")
val startEmulator by cli.flagArgument("-emu", "auto-start emulator after successful compilation")
val outputDir by cli.flagValueArgument("-out", "directory", "directory for output files instead of current directory", ".")
val dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code")
val dontOptimize by cli.flagArgument("-noopt", "don't perform any optimizations")
val watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes), greatly increases compilation speed")
val slowCodegenWarnings by cli.flagArgument("-slowwarn", "show debug warnings about slow/problematic assembly code generation")
val compilationTarget by cli.flagValueArgument("-target", "compilertarget",
"target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available", C64Target.name)
val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1)
private fun compileMain(args: Array<String>): Boolean {
val cli = ArgParser("prog8compiler", prefixStyle = ArgParser.OptionPrefixStyle.JVM)
val startEmulator1 by cli.option(ArgType.Boolean, fullName = "emu", description = "auto-start emulator after successful compilation")
val startEmulator2 by cli.option(ArgType.Boolean, fullName = "emu2", description = "auto-start alternative emulator after successful compilation")
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed")
val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name)
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths to search in for imported modules").multiple().delimiter(File.pathSeparator)
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
try {
cli.parse(args)
} catch (e: Exception) {
exitProcess(1)
} catch (e: IllegalStateException) {
System.err.println(e.message)
return false
}
val outputPath = pathFrom(outputDir)
if(!outputPath.toFile().isDirectory) {
System.err.println("Output path doesn't exist")
exitProcess(1)
return false
}
if(watchMode) {
val faultyOption = moduleFiles.firstOrNull { it.startsWith('-') }
if(faultyOption!=null) {
System.err.println("Unknown command line option given: $faultyOption")
return false
}
val srcdirs = sourceDirs.toMutableList()
if(srcdirs.firstOrNull()!=".")
srcdirs.add(0, ".")
if(watchMode==true) {
val watchservice = FileSystems.getDefault().newWatchService()
val allImportedFiles = mutableSetOf<Path>()
while(true) {
println("Continuous watch mode active. Modules: $moduleFiles")
val results = mutableListOf<CompilationResult>()
for(filepathRaw in moduleFiles) {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, outputPath)
val compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, srcdirs, outputPath)
results.add(compilationResult)
}
val allImportedFiles = results.flatMap { it.importedFiles }
val allNewlyImportedFiles = results.flatMap { it.importedFiles }
allImportedFiles.addAll(allNewlyImportedFiles)
println("Imported files (now watching:)")
for (importedFile in allImportedFiles) {
print(" ")
println(importedFile)
val watchDir = importedFile.parent ?: Path.of(".")
val watchDir = importedFile.parent ?: Path.of("")
watchDir.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
}
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
@ -98,22 +111,30 @@ private fun compileMain(args: Array<String>) {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult: CompilationResult
try {
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, outputPath)
compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, srcdirs, outputPath)
if(!compilationResult.success)
exitProcess(1)
return false
} catch (x: ParsingFailedError) {
exitProcess(1)
return false
} catch (x: AstException) {
exitProcess(1)
return false
}
if (startEmulator) {
if (compilationResult.programName.isEmpty())
if(startEmulator1==true || startEmulator2==true) {
if (compilationResult.programName.isEmpty()) {
println("\nCan't start emulator because no program was assembled.")
else if(startEmulator) {
CompilationTarget.instance.machine.launchEmulator(compilationResult.programName)
return true
}
}
val programNameInPath = outputPath.resolve(compilationResult.programName)
if (startEmulator1==true)
compilationResult.compTarget.machine.launchEmulator(1, programNameInPath)
else if (startEmulator2==true)
compilationResult.compTarget.machine.launchEmulator(2, programNameInPath)
}
}
return true
}

View File

@ -1,102 +0,0 @@
package prog8.ast.base
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.processing.*
import prog8.ast.statements.Directive
import prog8.compiler.CompilationOptions
import prog8.compiler.BeforeAsmGenerationAstChanger
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: ErrorReporter) {
val checker = AstChecker(this, compilerOptions, errors)
checker.visit(this)
}
internal fun Program.processAstBeforeAsmGeneration(errors: ErrorReporter) {
val fixer = BeforeAsmGenerationAstChanger(this, errors)
fixer.visit(this)
fixer.applyModifications()
}
internal fun Program.reorderStatements(errors: ErrorReporter) {
val reorder = StatementReorderer(this, errors)
reorder.visit(this)
reorder.applyModifications()
}
internal fun Program.addTypecasts(errors: ErrorReporter) {
val caster = TypecastsAdder(this, errors)
caster.visit(this)
caster.applyModifications()
}
internal fun Program.verifyFunctionArgTypes() {
val fixer = VerifyFunctionArgTypes(this)
fixer.visit(this)
}
internal fun Module.checkImportedValid() {
val imr = ImportedModuleDirectiveRemover()
imr.visit(this, this.parent)
imr.applyModifications()
}
internal fun Program.checkIdentifiers(errors: ErrorReporter) {
val checker2 = AstIdentifiersChecker(this, errors)
checker2.visit(this)
if(errors.isEmpty()) {
val transforms = AstVariousTransforms(this)
transforms.visit(this)
transforms.applyModifications()
val lit2decl = LiteralsToAutoVars(this)
lit2decl.visit(this)
lit2decl.applyModifications()
}
if (modules.map { it.name }.toSet().size != modules.size) {
throw FatalAstException("modules should all be unique")
}
}
internal fun Program.variousCleanups() {
val process = VariousCleanups()
process.visit(this)
process.applyModifications()
}
internal fun Program.moveMainAndStartToFirst() {
// the module containing the program entrypoint is moved to the first in the sequence.
// the "main" block containing the entrypoint is moved to the top in there,
// and finally the entrypoint subroutine "start" itself is moved to the top in that block.
val directives = modules[0].statements.filterIsInstance<Directive>()
val start = this.entrypoint()
if(start!=null) {
val mod = start.definingModule()
val block = start.definingBlock()
if(!modules.remove(mod))
throw FatalAstException("module wrong")
modules.add(0, mod)
mod.remove(block)
var afterDirective = mod.statements.indexOfFirst { it !is Directive }
if(afterDirective<0)
mod.statements.add(block)
else
mod.statements.add(afterDirective, block)
block.remove(start)
afterDirective = block.statements.indexOfFirst { it !is Directive }
if(afterDirective<0)
block.statements.add(start)
else
block.statements.add(afterDirective, start)
// overwrite the directives in the module containing the entrypoint
for(directive in directives) {
modules[0].statements.removeAll { it is Directive && it.directive == directive.directive }
modules[0].statements.add(0, directive)
}
}
}

View File

@ -1,22 +0,0 @@
package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.statements.Directive
internal class ImportedModuleDirectiveRemover: AstWalker() {
/**
* Most global directives don't apply for imported modules, so remove them
*/
private val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
private val noModifications = emptyList<IAstModification>()
override fun before(directive: Directive, parent: Node): Iterable<IAstModification> {
if(directive.directive in moduleLevelDirectives) {
return listOf(IAstModification.Remove(directive, parent as INameScope))
}
return noModifications
}
}

View File

@ -1,364 +0,0 @@
package prog8.ast.processing
import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
internal class StatementReorderer(val program: Program, val errors: ErrorReporter) : AstWalker() {
// Reorders the statements in a way the compiler needs.
// - 'main' block must be the very first statement UNLESS it has an address set.
// - library blocks are put last.
// - blocks are ordered by address, where blocks without address are placed last.
// - in every block and module, most directives and vardecls are moved to the top. (not in subroutines!)
// - the 'start' subroutine is moved to the top.
// - (syntax desugaring) a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement.
// - (syntax desugaring) struct value assignment is expanded into several struct member assignments.
// - in-place assignments are reordered a bit so that they are mostly of the form A = A <operator> <rest>
// - sorts the choices in when statement.
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc).
private val noModifications = emptyList<IAstModification>()
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
val (blocks, other) = module.statements.partition { it is Block }
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: Int.MAX_VALUE }).toMutableList()
val mainBlock = module.statements.filterIsInstance<Block>().firstOrNull { it.name=="main" }
if(mainBlock!=null && mainBlock.address==null) {
module.statements.remove(mainBlock)
module.statements.add(0, mainBlock)
}
reorderVardeclsAndDirectives(module.statements)
return noModifications
}
private fun reorderVardeclsAndDirectives(statements: MutableList<Statement>) {
val varDecls = statements.filterIsInstance<VarDecl>()
statements.removeAll(varDecls)
statements.addAll(0, varDecls)
val directives = statements.filterIsInstance<Directive>().filter {it.directive in directivesToMove}
statements.removeAll(directives)
statements.addAll(0, directives)
}
override fun before(block: Block, parent: Node): Iterable<IAstModification> {
parent as Module
if(block.isInLibrary) {
return listOf(
IAstModification.Remove(block, parent),
IAstModification.InsertLast(block, parent)
)
}
reorderVardeclsAndDirectives(block.statements)
return noModifications
}
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
if(subroutine.name=="start" && parent is Block) {
if(parent.statements.filterIsInstance<Subroutine>().first().name!="start") {
return listOf(
IAstModification.Remove(subroutine, parent),
IAstModification.InsertFirst(subroutine, parent)
)
}
}
return noModifications
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
when (val expr2 = arrayIndexedExpression.indexer.origExpression) {
is NumericLiteralValue -> {
arrayIndexedExpression.indexer.indexNum = expr2
arrayIndexedExpression.indexer.origExpression = null
return noModifications
}
is IdentifierReference -> {
arrayIndexedExpression.indexer.indexVar = expr2
arrayIndexedExpression.indexer.origExpression = null
return noModifications
}
is Expression -> {
// replace complex indexing with a temp variable
return getAutoIndexerVarFor(arrayIndexedExpression)
}
else -> return noModifications
}
}
private fun getAutoIndexerVarFor(expr: ArrayIndexedExpression): MutableList<IAstModification> {
val modifications = mutableListOf<IAstModification>()
val subroutine = expr.definingSubroutine()!!
val statement = expr.containingStatement()
val indexerVarPrefix = "prog8_autovar_index_"
val repo = subroutine.asmGenInfo.usedAutoArrayIndexerForStatements
// TODO make this even smarter so that an indexerVar can be reused for a different following statement... requires updating the partOfStatement?
var indexerVar = repo.firstOrNull { it.replaces isSameAs expr.indexer }
if(indexerVar==null) {
// add another loop index var to be used for this expression
val indexerVarName = "$indexerVarPrefix${expr.indexer.hashCode()}"
indexerVar = AsmGenInfo.ArrayIndexerInfo(indexerVarName, expr.indexer, statement)
repo.add(indexerVar)
// create the indexer var at block level scope
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE,
null, indexerVarName, null, null, isArray = false, autogeneratedDontRemove = true, position = expr.position)
modifications.add(IAstModification.InsertFirst(vardecl, subroutine))
}
indexerVar.used++ // keep track of how many times it it used, to avoid assigning it multiple times
// replace the indexer with just the variable
// assign the indexing expression to the helper variable, but only if that hasn't been done already
val indexerExpression = expr.indexer.origExpression!!
val target = AssignTarget(IdentifierReference(listOf(indexerVar.name), indexerExpression.position), null, null, indexerExpression.position)
if(indexerVar.used==1) {
val assign = Assignment(target, indexerExpression, indexerExpression.position)
modifications.add(IAstModification.InsertBefore(statement, assign, statement.definingScope()))
}
modifications.add(IAstModification.SetExpression( {
expr.indexer.indexVar = it as IdentifierReference
expr.indexer.indexNum = null
expr.indexer.origExpression = null
}, target.identifier!!.copy(), expr.indexer))
return modifications
}
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
val choices = whenStatement.choiceValues(program).sortedBy {
it.first?.first() ?: Int.MAX_VALUE
}
whenStatement.choices.clear()
choices.mapTo(whenStatement.choices) { it.second }
return noModifications
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val declValue = decl.value
if(declValue!=null && decl.type== VarDeclType.VAR && decl.datatype in NumericDatatypes) {
val declConstValue = declValue.constValue(program)
if(declConstValue==null) {
// move the vardecl (without value) to the scope and replace this with a regular assignment
// Unless we're dealing with a floating point variable because that will actually make things less efficient at the moment (because floats are mostly calcualated via the stack)
if(decl.datatype!=DataType.FLOAT) {
decl.value = null
decl.allowInitializeWithZero = false
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, declValue, decl.position)
return listOf(
IAstModification.ReplaceNode(decl, assign, parent),
IAstModification.InsertFirst(decl, decl.definingScope())
)
}
}
}
return noModifications
}
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
val valueType = assignment.value.inferType(program)
val targetType = assignment.target.inferType(program)
var assignments = emptyList<Assignment>()
if(targetType.istype(DataType.STRUCT) && (valueType.istype(DataType.STRUCT) || valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes )) {
assignments = if (assignment.value is ArrayLiteralValue) {
flattenStructAssignmentFromStructLiteral(assignment) // 'structvar = [ ..... ] '
} else {
flattenStructAssignmentFromIdentifier(assignment) // 'structvar1 = structvar2'
}
}
if(targetType.typeOrElse(DataType.STRUCT) in ArrayDatatypes && valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes ) {
assignments = if (assignment.value is ArrayLiteralValue) {
flattenArrayAssignmentFromArrayLiteral(assignment) // 'arrayvar = [ ..... ] '
} else {
flattenArrayAssignmentFromIdentifier(assignment) // 'arrayvar1 = arrayvar2'
}
}
if(assignments.isNotEmpty()) {
val modifications = mutableListOf<IAstModification>()
val scope = assignment.definingScope()
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, scope) }
modifications.add(IAstModification.Remove(assignment, scope))
return modifications
}
return noModifications
}
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
// rewrite in-place assignment expressions a bit so that the assignment target usually is the leftmost operand
val binExpr = assignment.value as? BinaryExpression
if(binExpr!=null) {
if(binExpr.left isSameAs assignment.target) {
// A = A <operator> 5, unchanged
return noModifications
}
if(binExpr.operator in associativeOperators) {
if (binExpr.right isSameAs assignment.target) {
// A = v <associative-operator> A ==> A = A <associative-operator> v
return listOf(IAstModification.SwapOperands(binExpr))
}
val leftBinExpr = binExpr.left as? BinaryExpression
if(leftBinExpr?.operator == binExpr.operator) {
return if(leftBinExpr.left isSameAs assignment.target) {
// A = (A <associative-operator> x) <same-operator> y ==> A = A <associative-operator> (x <same-operator> y)
val newRight = BinaryExpression(leftBinExpr.right, binExpr.operator, binExpr.right, binExpr.position)
val newValue = BinaryExpression(leftBinExpr.left, binExpr.operator, newRight, binExpr.position)
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
} else {
// A = (x <associative-operator> A) <same-operator> y ==> A = A <associative-operator> (x <same-operator> y)
val newRight = BinaryExpression(leftBinExpr.left, binExpr.operator, binExpr.right, binExpr.position)
val newValue = BinaryExpression(leftBinExpr.right, binExpr.operator, newRight, binExpr.position)
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
}
}
val rightBinExpr = binExpr.right as? BinaryExpression
if(rightBinExpr?.operator == binExpr.operator) {
return if(rightBinExpr.left isSameAs assignment.target) {
// A = x <associative-operator> (A <same-operator> y) ==> A = A <associative-operator> (x <same-operator> y)
val newRight = BinaryExpression(binExpr.left, binExpr.operator, rightBinExpr.right, binExpr.position)
val newValue = BinaryExpression(rightBinExpr.left, binExpr.operator, newRight, binExpr.position)
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
} else {
// A = x <associative-operator> (y <same-operator> A) ==> A = A <associative-operator> (x <same-operator> y)
val newRight = BinaryExpression(binExpr.left, binExpr.operator, rightBinExpr.left, binExpr.position)
val newValue = BinaryExpression(rightBinExpr.right, binExpr.operator, newRight, binExpr.position)
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
}
}
}
}
return noModifications
}
private fun flattenArrayAssignmentFromArrayLiteral(assign: Assignment): List<Assignment> {
val identifier = assign.target.identifier!!
val targetVar = identifier.targetVarDecl(program.namespace)!!
val alv = assign.value as? ArrayLiteralValue
return flattenArrayAssign(targetVar, alv, identifier, assign.position)
}
private fun flattenArrayAssignmentFromIdentifier(assign: Assignment): List<Assignment> {
val identifier = assign.target.identifier!!
val targetVar = identifier.targetVarDecl(program.namespace)!!
val sourceIdent = assign.value as IdentifierReference
val sourceVar = sourceIdent.targetVarDecl(program.namespace)!!
if(!sourceVar.isArray) {
errors.err("value must be an array", sourceIdent.position)
return emptyList()
}
val alv = sourceVar.value as? ArrayLiteralValue
return flattenArrayAssign(targetVar, alv, identifier, assign.position)
}
private fun flattenArrayAssign(targetVar: VarDecl, alv: ArrayLiteralValue?, identifier: IdentifierReference, position: Position): List<Assignment> {
if(targetVar.arraysize==null) {
errors.err("array has no defined size", identifier.position)
return emptyList()
}
if(alv==null || alv.value.size != targetVar.arraysize!!.constIndex()) {
errors.err("element count mismatch", position)
return emptyList()
}
// TODO use a pointer loop instead of individual assignments
return alv.value.mapIndexed { index, value ->
val idx = ArrayIndexedExpression(identifier, ArrayIndex(NumericLiteralValue(DataType.UBYTE, index, position), position), position)
Assignment(AssignTarget(null, idx, null, position), value, value.position)
}
}
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment): List<Assignment> {
val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!!
val struct = targetVar.struct!!
val slv = structAssignment.value as? ArrayLiteralValue
if(slv==null || slv.value.size != struct.numberOfElements) {
errors.err("element count mismatch", structAssignment.position)
return emptyList()
}
return struct.statements.zip(slv.value).map { (targetDecl, sourceValue) ->
targetDecl as VarDecl
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position),
sourceValue, sourceValue.position)
assign.linkParents(structAssignment)
assign
}
}
private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment): List<Assignment> {
// TODO use memcopy beyond a certain number of elements
val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!!
val struct = targetVar.struct!!
when (structAssignment.value) {
is IdentifierReference -> {
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!!
when {
sourceVar.struct!=null -> {
// struct memberwise copy
val sourceStruct = sourceVar.struct!!
if(sourceStruct!==targetVar.struct) {
// structs are not the same in assignment
return listOf() // error will be printed elsewhere
}
if(struct.statements.size!=sourceStruct.statements.size)
return listOf() // error will be printed elsewhere
return struct.statements.zip(sourceStruct.statements).map { member ->
val targetDecl = member.first as VarDecl
val sourceDecl = member.second as VarDecl
if(targetDecl.name != sourceDecl.name)
throw FatalAstException("struct member mismatch")
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position), sourceIdref, member.second.position)
assign.linkParents(structAssignment)
assign
}
}
sourceVar.isArray -> {
val array = (sourceVar.value as ArrayLiteralValue).value
if(struct.statements.size!=array.size)
return listOf() // error will be printed elsewhere
return struct.statements.zip(array).map {
val decl = it.first as VarDecl
val mangled = mangledStructMemberName(identifierName, decl.name)
val targetName = IdentifierReference(listOf(mangled), structAssignment.position)
val target = AssignTarget(targetName, null, null, structAssignment.position)
val assign = Assignment(target, it.second, structAssignment.position)
assign.linkParents(structAssignment)
assign
}
}
else -> {
throw FatalAstException("can only assign arrays or structs to structs")
}
}
}
is ArrayLiteralValue -> {
throw IllegalArgumentException("not going to flatten a structLv assignment here")
}
else -> throw FatalAstException("strange struct value")
}
}
}

View File

@ -1,44 +0,0 @@
package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.NopStatement
internal class VariousCleanups: AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.Remove(nopStatement, parent as INameScope))
}
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
return if(parent is INameScope)
listOf(ScopeFlatten(scope, parent as INameScope))
else
noModifications
}
class ScopeFlatten(val scope: AnonymousScope, val into: INameScope) : IAstModification {
override fun perform() {
val idx = into.statements.indexOf(scope)
if(idx>=0) {
into.statements.addAll(idx+1, scope.statements)
into.statements.remove(scope)
}
}
}
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
if(typecast.expression is NumericLiteralValue) {
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type)
if(value.isValid)
return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent))
}
return noModifications
}
}

View File

@ -5,27 +5,30 @@ import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.ast.walk.IAstVisitor
import prog8.compiler.astprocessing.isInRegularRAMof
import prog8.compiler.target.ICompilationTarget
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: ErrorReporter) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
subroutineVariables.add(decl.name to decl)
if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
// a numeric vardecl without an initial value is initialized with zero,
// unless there's already an assignment below, that initializes the value
// A numeric vardecl without an initial value is initialized with zero,
// unless there's already an assignment below, that initializes the value.
// This allows you to restart the program and have the same starting values of the variables
if(decl.allowInitializeWithZero)
{
val nextAssign = decl.definingScope().nextSibling(decl) as? Assignment
if (nextAssign != null && nextAssign.target.isSameAs(IdentifierReference(listOf(decl.name), Position.DUMMY)))
val nextAssign = decl.definingScope.nextSibling(decl) as? Assignment
if (nextAssign != null && nextAssign.target isSameAs IdentifierReference(listOf(decl.name), Position.DUMMY))
decl.value = null
else
else {
decl.value = decl.zeroElementValue()
}
}
}
return noModifications
@ -37,7 +40,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
if(!assignment.isAugmentable
&& assignment.target.identifier != null
&& assignment.target.isInRegularRAM(program.namespace)) {
&& assignment.target.isInRegularRAMof(compTarget.machine)) {
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null && binExpr.operator !in comparisonOperators) {
if (binExpr.left !is BinaryExpression) {
@ -49,14 +52,14 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
// use the other part of the expression to split.
val assignRight = Assignment(assignment.target, binExpr.right, assignment.position)
return listOf(
IAstModification.InsertBefore(assignment, assignRight, assignment.definingScope()),
IAstModification.InsertBefore(assignment, assignRight, assignment.definingScope),
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
}
} else {
val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position)
return listOf(
IAstModification.InsertBefore(assignment, assignLeft, assignment.definingScope()),
IAstModification.InsertBefore(assignment, assignLeft, assignment.definingScope),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
}
}
@ -66,30 +69,37 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
}
private val subroutineVariables = mutableListOf<Pair<String, VarDecl>>()
private val addedIfConditionVars = mutableSetOf<Pair<Subroutine, String>>()
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
subroutineVariables.clear()
addedIfConditionVars.clear()
return noModifications
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
val decls = scope.statements.filterIsInstance<VarDecl>()
val decls = scope.statements.filterIsInstance<VarDecl>().filter { it.type == VarDeclType.VAR }
subroutineVariables.addAll(decls.map { it.name to it })
val sub = scope.definingSubroutine()
val sub = scope.definingSubroutine
if (sub != null) {
// move vardecls of the scope into the upper scope. Make sure the position remains the same!
val numericVarsWithValue = decls.filter { it.value != null && it.datatype in NumericDatatypes }
val replaceVardecls =numericVarsWithValue.map {
val initValue = it.value!! // assume here that value has always been set by now
it.value = null // make sure no value init assignment for this vardecl will be created later (would be superfluous)
val target = AssignTarget(IdentifierReference(listOf(it.name), it.position), null, null, it.position)
val assign = Assignment(target, initValue, it.position)
initValue.parent = assign
IAstModification.ReplaceNode(it, assign, scope)
// move any remaining vardecls of the scope into the upper scope. Make sure the position remains the same!
val replacements = mutableListOf<IAstModification>()
val movements = mutableListOf<IAstModification.InsertFirst>()
for(decl in decls) {
if(decl.value!=null && decl.datatype in NumericDatatypes) {
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, decl.value!!, decl.position)
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
decl.value = null
decl.allowInitializeWithZero = false
} else {
replacements.add(IAstModification.Remove(decl, scope))
}
movements.add(IAstModification.InsertFirst(decl, sub))
}
val moveVardeclsUp = decls.map { IAstModification.InsertFirst(it, sub) }
return replaceVardecls + moveVardeclsUp
return replacements + movements
}
return noModifications
}
@ -106,20 +116,21 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
}
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine.
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine.
// and if an assembly block doesn't contain a rts/rti, and some other situations.
val mods = mutableListOf<IAstModification>()
val returnStmt = Return(null, subroutine.position)
if (subroutine.asmAddress == null
&& subroutine.statements.isNotEmpty()
&& subroutine.amountOfRtsInAsm() == 0
if (subroutine.asmAddress == null && !subroutine.inline) {
if(subroutine.statements.isEmpty() ||
(subroutine.amountOfRtsInAsm() == 0
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
&& subroutine.statements.last() !is Subroutine) {
mods += IAstModification.InsertLast(returnStmt, subroutine)
&& subroutine.statements.last() !is Subroutine)) {
mods += IAstModification.InsertLast(returnStmt, subroutine)
}
}
// precede a subroutine with a return to avoid falling through into the subroutine from code above it
val outerScope = subroutine.definingScope()
val outerScope = subroutine.definingScope
val outerStatements = outerScope.statements
val subroutineStmtIdx = outerStatements.indexOf(subroutine)
if (subroutineStmtIdx > 0
@ -136,7 +147,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
// see if we can remove superfluous typecasts (outside of expressions)
// such as casting byte<->ubyte, word<->uword
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of.
val sourceDt = typecast.expression.inferType(program).typeOrElse(DataType.STRUCT)
val sourceDt = typecast.expression.inferType(program).getOr(DataType.UNDEFINED)
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes) {
if(typecast.parent !is Expression) {
@ -152,16 +163,6 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
// The only place for now where we can do this is for:
// asmsub register pair parameter.
if(typecast.type in WordDatatypes) {
val fcall = typecast.parent as? IFunctionCall
if (fcall != null) {
val sub = fcall.target.targetStatement(program.namespace) as? Subroutine
if (sub != null && sub.isAsmSubroutine) {
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
}
}
}
if(sourceDt in PassByReferenceDatatypes) {
if(typecast.type==DataType.UWORD) {
if(typecast.expression is IdentifierReference) {
@ -192,9 +193,55 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
val booleanExpr = BinaryExpression(ifStatement.condition, "!=", NumericLiteralValue.optimalInteger(0, ifStatement.condition.position), ifStatement.condition.position)
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
}
if((binExpr.operator=="==" || binExpr.operator=="!=") &&
(binExpr.left as? NumericLiteralValue)?.number==0 &&
(binExpr.right as? NumericLiteralValue)?.number!=0)
throw CompilerException("if 0==X should have been swapped to if X==0")
// split the conditional expression into separate variables if the operand(s) is not simple.
// DISABLED FOR NOW AS IT GENEREATES LARGER CODE IN THE SIMPLE CASES LIKE IF X {...} or IF NOT X {...}
// val modifications = mutableListOf<IAstModification>()
// if(!binExpr.left.isSimple) {
// val sub = binExpr.definingSubroutine()!!
// val (variable, isNew, assignment) = addIfOperandVar(sub, "left", binExpr.left)
// if(isNew)
// modifications.add(IAstModification.InsertFirst(variable, sub))
// modifications.add(IAstModification.InsertBefore(ifStatement, assignment, parent as INameScope))
// modifications.add(IAstModification.ReplaceNode(binExpr.left, IdentifierReference(listOf(variable.name), binExpr.position), binExpr))
// addedIfConditionVars.add(Pair(sub, variable.name))
// }
// if(!binExpr.right.isSimple) {
// val sub = binExpr.definingSubroutine()!!
// val (variable, isNew, assignment) = addIfOperandVar(sub, "right", binExpr.right)
// if(isNew)
// modifications.add(IAstModification.InsertFirst(variable, sub))
// modifications.add(IAstModification.InsertBefore(ifStatement, assignment, parent as INameScope))
// modifications.add(IAstModification.ReplaceNode(binExpr.right, IdentifierReference(listOf(variable.name), binExpr.position), binExpr))
// addedIfConditionVars.add(Pair(sub, variable.name))
// }
// return modifications
return noModifications
}
// private fun addIfOperandVar(sub: Subroutine, side: String, operand: Expression): Triple<VarDecl, Boolean, Assignment> {
// val dt = operand.inferType(program).typeOrElse(DataType.UNDEFINED)
// val varname = "prog8_ifvar_${side}_${dt.name.toLowerCase()}"
// val tgt = AssignTarget(IdentifierReference(listOf(varname), operand.position), null, null, operand.position)
// val assign = Assignment(tgt, operand, operand.position)
// if(Pair(sub, varname) in addedIfConditionVars) {
// val vardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, varname, null, null, false, true, operand.position)
// return Triple(vardecl, false, assign)
// }
// val existing = sub.statements.firstOrNull { it is VarDecl && it.name == varname} as VarDecl?
// return if (existing == null) {
// val vardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, varname, null, null, false, true, operand.position)
// Triple(vardecl, true, assign)
// } else {
// Triple(existing, false, assign)
// }
// }
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
val binExpr = untilLoop.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in comparisonOperators) {
@ -214,4 +261,95 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
}
return noModifications
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource==listOf("cmp")) {
// if the datatype of the arguments of cmp() are different, cast the byte one to word.
val arg1 = functionCallStatement.args[0]
val arg2 = functionCallStatement.args[1]
val dt1 = arg1.inferType(program).getOr(DataType.UNDEFINED)
val dt2 = arg2.inferType(program).getOr(DataType.UNDEFINED)
if(dt1 in ByteDatatypes) {
if(dt2 in ByteDatatypes)
return noModifications
val cast1 = TypecastExpression(arg1, if(dt1==DataType.UBYTE) DataType.UWORD else DataType.WORD, true, functionCallStatement.position)
return listOf(IAstModification.ReplaceNode(arg1, cast1, functionCallStatement))
} else {
if(dt2 in WordDatatypes)
return noModifications
val cast2 = TypecastExpression(arg2, if(dt2==DataType.UBYTE) DataType.UWORD else DataType.WORD, true, functionCallStatement.position)
return listOf(IAstModification.ReplaceNode(arg2, cast2, functionCallStatement))
}
}
return noModifications
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
val containingStatement = getContainingStatement(arrayIndexedExpression)
if(getComplexArrayIndexedExpressions(containingStatement).size > 1) {
errors.err("it's not possible to use more than one complex array indexing expression in a single statement; break it up via a temporary variable for instance", containingStatement.position)
return noModifications
}
val index = arrayIndexedExpression.indexer.indexExpr
if(index !is NumericLiteralValue && index !is IdentifierReference) {
// replace complex indexing expression with a temp variable to hold the computed index first
return getAutoIndexerVarFor(arrayIndexedExpression)
}
return noModifications
}
private fun getComplexArrayIndexedExpressions(stmt: Statement): List<ArrayIndexedExpression> {
class Searcher : IAstVisitor {
val complexArrayIndexedExpressions = mutableListOf<ArrayIndexedExpression>()
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
val ix = arrayIndexedExpression.indexer.indexExpr
if(ix !is NumericLiteralValue && ix !is IdentifierReference)
complexArrayIndexedExpressions.add(arrayIndexedExpression)
}
override fun visit(branchStatement: BranchStatement) {}
override fun visit(forLoop: ForLoop) {}
override fun visit(ifStatement: IfStatement) {
ifStatement.condition.accept(this)
}
override fun visit(untilLoop: UntilLoop) {
untilLoop.condition.accept(this)
}
}
val searcher = Searcher()
stmt.accept(searcher)
return searcher.complexArrayIndexedExpressions
}
private fun getContainingStatement(expression: Expression): Statement {
var node: Node = expression
while(node !is Statement)
node = node.parent
return node
}
private fun getAutoIndexerVarFor(expr: ArrayIndexedExpression): MutableList<IAstModification> {
val modifications = mutableListOf<IAstModification>()
val statement = expr.containingStatement
val dt = expr.indexer.indexExpr.inferType(program)
val register = if(dt istype DataType.UBYTE || dt istype DataType.BYTE ) "r9L" else "r9"
// replace the indexer with just the variable (simply use a cx16 virtual register r9, that we HOPE is not used for other things in the expression...)
// assign the indexing expression to the helper variable, but only if that hasn't been done already
val target = AssignTarget(IdentifierReference(listOf("cx16", register), expr.indexer.position), null, null, expr.indexer.position)
val assign = Assignment(target, expr.indexer.indexExpr, expr.indexer.position)
modifications.add(IAstModification.InsertBefore(statement, assign, statement.definingScope))
modifications.add(IAstModification.ReplaceNode(expr.indexer.indexExpr, target.identifier!!.copy(), expr.indexer))
return modifications
}
}

View File

@ -1,9 +1,32 @@
package prog8.compiler
import com.github.michaelbull.result.*
import prog8.ast.AstToSourceCode
import prog8.ast.IBuiltinFunctions
import prog8.ast.IMemSizer
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.ast.base.Position
import prog8.ast.expressions.Expression
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.Directive
import prog8.compiler.astprocessing.*
import prog8.compiler.functions.*
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.ICompilationTarget
import prog8.compiler.target.asmGeneratorFor
import prog8.optimizer.*
import prog8.parser.ParseError
import prog8.parser.ParsingFailedError
import prog8.parser.SourceCode
import prog8.parser.SourceCode.Companion.libraryFilePrefix
import java.io.File
import java.io.InputStream
import java.nio.file.Path
import kotlin.math.abs
import kotlin.io.path.Path
import kotlin.io.path.nameWithoutExtension
import kotlin.system.measureTimeMillis
enum class OutputType {
RAW,
@ -28,44 +51,345 @@ data class CompilationOptions(val output: OutputType,
val zeropage: ZeropageType,
val zpReserved: List<IntRange>,
val floats: Boolean,
val noSysInit: Boolean) {
val noSysInit: Boolean,
val compTarget: ICompilationTarget) {
var slowCodegenWarnings = false
var optimize = false
}
class CompilerException(message: String?) : Exception(message)
fun Number.toHex(): String {
// 0..15 -> "0".."15"
// 16..255 -> "$10".."$ff"
// 256..65536 -> "$0100".."$ffff"
// negative values are prefixed with '-'.
val integer = this.toInt()
if(integer<0)
return '-' + abs(integer).toHex()
return when (integer) {
in 0 until 16 -> integer.toString()
in 0 until 0x100 -> "$"+integer.toString(16).padStart(2,'0')
in 0 until 0x10000 -> "$"+integer.toString(16).padStart(4,'0')
else -> throw CompilerException("number too large for 16 bits $this")
class CompilationResult(val success: Boolean,
val programAst: Program,
val programName: String,
val compTarget: ICompilationTarget,
val importedFiles: List<Path>)
fun compileProgram(filepath: Path,
optimize: Boolean,
writeAssembly: Boolean,
slowCodegenWarnings: Boolean,
compilationTarget: String,
sourceDirs: List<String>,
outputDir: Path): CompilationResult {
var programName = ""
lateinit var programAst: Program
lateinit var importedFiles: List<Path>
val errors = ErrorReporter()
val compTarget =
when(compilationTarget) {
C64Target.name -> C64Target
Cx16Target.name -> Cx16Target
else -> throw IllegalArgumentException("invalid compilation target")
}
try {
val totalTime = measureTimeMillis {
// import main module and everything it needs
val (ast, compilationOptions, imported) = parseImports(filepath, errors, compTarget, sourceDirs)
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
compilationOptions.optimize = optimize
programAst = ast
importedFiles = imported
processAst(programAst, errors, compilationOptions)
if (compilationOptions.optimize)
optimizeAst(
programAst,
errors,
BuiltinFunctionsFacade(BuiltinFunctions),
compTarget,
compilationOptions
)
postprocessAst(programAst, errors, compilationOptions)
// printAst(programAst)
if (writeAssembly) {
val result = writeAssembly(programAst, errors, outputDir, compilationOptions)
when (result) {
is WriteAssemblyResult.Ok -> programName = result.filename
is WriteAssemblyResult.Fail -> {
System.err.println(result.error)
return CompilationResult(false, programAst, programName, compTarget, importedFiles)
}
}
}
}
System.out.flush()
System.err.flush()
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
return CompilationResult(true, programAst, programName, compTarget, importedFiles)
} catch (px: ParseError) {
System.err.print("\u001b[91m") // bright red
System.err.println("${px.position.toClickableStr()} parse error: ${px.message}".trim())
System.err.print("\u001b[0m") // reset
} catch (pfx: ParsingFailedError) {
System.err.print("\u001b[91m") // bright red
System.err.println(pfx.message)
System.err.print("\u001b[0m") // reset
} catch (ax: AstException) {
System.err.print("\u001b[91m") // bright red
System.err.println(ax.toString())
System.err.print("\u001b[0m") // reset
} catch (x: Exception) {
print("\u001b[91m") // bright red
println("\n* internal error *")
print("\u001b[0m") // reset
System.out.flush()
throw x
} catch (x: NotImplementedError) {
print("\u001b[91m") // bright red
println("\n* internal error: missing feature/code *")
print("\u001b[0m") // reset
System.out.flush()
throw x
}
val failedProgram = Program("failed", BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
return CompilationResult(false, failedProgram, programName, compTarget, emptyList())
}
private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuiltinFunctions {
lateinit var program: Program
override val names = functions.keys
override val purefunctionNames = functions.filter { it.value.pure }.map { it.key }.toSet()
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? {
val func = BuiltinFunctions[name]
if(func!=null) {
val exprfunc = func.constExpressionFunc
if(exprfunc!=null) {
return try {
exprfunc(args, position, program, memsizer)
} catch(x: NotConstArgumentException) {
// const-evaluating the builtin function call failed.
null
} catch(x: CannotEvaluateException) {
// const-evaluating the builtin function call failed.
null
}
}
else if(func.known_returntype==null)
return null // builtin function $name can't be used here because it doesn't return a value
}
return null
}
override fun returnType(name: String, args: MutableList<Expression>) =
builtinFunctionReturnType(name, args, program)
}
fun parseImports(filepath: Path,
errors: IErrorReporter,
compTarget: ICompilationTarget,
sourceDirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
println("Compiler target: ${compTarget.name}. Parsing...")
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
val programAst = Program(filepath.nameWithoutExtension, bf, compTarget)
bf.program = programAst
val importer = ModuleImporter(programAst, compTarget.name, errors, sourceDirs)
val importedModuleResult = importer.importModule(filepath)
importedModuleResult.onFailure { throw it }
errors.report()
val importedFiles = programAst.modules.map { it.source }
.filter { it.isFromFilesystem }
.map { Path(it.origin) }
val compilerOptions = determineCompilationOptions(programAst, compTarget)
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
// depending on the machine and compiler options we may have to include some libraries
for(lib in compTarget.machine.importLibs(compilerOptions, compTarget.name))
importer.importLibraryModule(lib)
// always import prog8_lib and math
importer.importLibraryModule("math")
importer.importLibraryModule("prog8_lib")
errors.report()
return Triple(programAst, compilerOptions, importedFiles)
}
fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions {
val toplevelModule = program.toplevelModule
val outputDirective = (toplevelModule.statements.singleOrNull { it is Directive && it.directive == "%output" } as? Directive)
val launcherDirective = (toplevelModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" } as? Directive)
val outputTypeStr = outputDirective?.args?.single()?.name?.uppercase()
val launcherTypeStr = launcherDirective?.args?.single()?.name?.uppercase()
val zpoption: String? = (toplevelModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
as? Directive)?.args?.single()?.name?.uppercase()
val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }
.flatMap { (it as Directive).args }.toSet()
val floatsEnabled = allOptions.any { it.name == "enable_floats" }
val noSysInit = allOptions.any { it.name == "no_sysinit" }
var zpType: ZeropageType =
if (zpoption == null)
if (floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
else
try {
ZeropageType.valueOf(zpoption)
} catch (x: IllegalArgumentException) {
ZeropageType.KERNALSAFE
// error will be printed by the astchecker
}
if (zpType == ZeropageType.FLOATSAFE && compTarget.name == Cx16Target.name) {
System.err.println("Warning: zp option floatsafe changed to basicsafe for cx16 target")
zpType = ZeropageType.BASICSAFE
}
val zpReserved = toplevelModule.statements
.asSequence()
.filter { it is Directive && it.directive == "%zpreserved" }
.map { (it as Directive).args }
.map { it[0].int!!..it[1].int!! }
.toList()
val outputType = if (outputTypeStr == null) OutputType.PRG else {
try {
OutputType.valueOf(outputTypeStr)
} catch (x: IllegalArgumentException) {
// set default value; actual check and error handling of invalid option is handled in the AstChecker later
OutputType.PRG
}
}
val launcherType = if (launcherTypeStr == null) LauncherType.BASIC else {
try {
LauncherType.valueOf(launcherTypeStr)
} catch (x: IllegalArgumentException) {
// set default value; actual check and error handling of invalid option is handled in the AstChecker later
LauncherType.BASIC
}
}
return CompilationOptions(
outputType,
launcherType,
zpType, zpReserved, floatsEnabled, noSysInit,
compTarget
)
}
private fun processAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
// perform initial syntax checks and processings
println("Processing for target ${compilerOptions.compTarget.name}...")
programAst.checkIdentifiers(errors, compilerOptions)
errors.report()
// TODO: turning char literals into UBYTEs via an encoding should really happen in code gen - but for that we'd need DataType.CHAR
// NOTE: we will then lose the opportunity to do constant-folding on any expression containing a char literal, but how often will those occur?
// Also they might be optimized away eventually in codegen or by the assembler even
programAst.charLiteralsToUByteLiterals(errors, compilerOptions.compTarget)
errors.report()
programAst.constantFold(errors, compilerOptions.compTarget)
errors.report()
programAst.reorderStatements(errors)
errors.report()
programAst.addTypecasts(errors)
errors.report()
programAst.variousCleanups(programAst, errors)
errors.report()
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget)
errors.report()
programAst.checkIdentifiers(errors, compilerOptions)
errors.report()
}
private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget, options: CompilationOptions) {
// optimize the parse tree
println("Optimizing...")
val remover = UnusedCodeRemover(programAst, errors, compTarget)
remover.visit(programAst)
remover.applyModifications()
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = programAst.splitBinaryExpressions(compTarget)
val optsDone3 = programAst.optimizeStatements(errors, functions, compTarget)
programAst.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away
errors.report()
if (optsDone1 + optsDone2 + optsDone3 == 0)
break
}
errors.report()
}
private fun postprocessAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
programAst.addTypecasts(errors)
errors.report()
programAst.variousCleanups(programAst, errors)
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) // check if final tree is still valid
errors.report()
val callGraph = CallGraph(programAst)
callGraph.checkRecursiveCalls(errors)
errors.report()
programAst.verifyFunctionArgTypes()
programAst.moveMainAndStartToFirst()
}
private sealed class WriteAssemblyResult {
class Ok(val filename: String): WriteAssemblyResult()
class Fail(val error: String): WriteAssemblyResult()
}
private fun writeAssembly(programAst: Program,
errors: IErrorReporter,
outputDir: Path,
compilerOptions: CompilationOptions): WriteAssemblyResult {
// asm generation directly from the Ast
programAst.processAstBeforeAsmGeneration(errors, compilerOptions.compTarget)
errors.report()
// printAst(programAst)
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
val assembly = asmGeneratorFor(compilerOptions.compTarget,
programAst,
errors,
compilerOptions.compTarget.machine.zeropage,
compilerOptions,
outputDir).compileToAssembly()
return if(assembly.valid && errors.noErrors()) {
val assemblerReturnStatus = assembly.assemble(compilerOptions)
if(assemblerReturnStatus!=0)
WriteAssemblyResult.Fail("assembler step failed with return code $assemblerReturnStatus")
else {
errors.report()
WriteAssemblyResult.Ok(assembly.name)
}
} else {
errors.report()
WriteAssemblyResult.Fail("compiler failed with errors")
}
}
fun loadAsmIncludeFile(filename: String, source: Path): String {
return if (filename.startsWith("library:")) {
val resource = tryGetEmbeddedResource(filename.substring(8))
?: throw IllegalArgumentException("library file '$filename' not found")
resource.bufferedReader().use { it.readText() }
fun printAst(programAst: Program) {
println()
val printer = AstToSourceCode(::print, programAst)
printer.visit(programAst)
println()
}
internal fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> {
return if (filename.startsWith(libraryFilePrefix)) {
return runCatching {
val stream = object {}.javaClass.getResourceAsStream("/prog8lib/${filename.substring(libraryFilePrefix.length)}") // TODO handle via SourceCode
stream!!.bufferedReader().use { r -> r.readText() }
}.mapError { NoSuchFileException(File(filename)) }
} else {
// first try in the isSameAs folder as where the containing file was imported from
val sib = source.resolveSibling(filename)
val sib = Path(source.origin).resolveSibling(filename)
if (sib.toFile().isFile)
sib.toFile().readText()
Ok(sib.toFile().readText())
else
File(filename).readText()
Ok(File(filename).readText())
}
}
internal fun tryGetEmbeddedResource(name: String): InputStream? {
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
}

View File

@ -1,9 +1,18 @@
package prog8.ast.base
package prog8.compiler
import prog8.ast.base.Position
import prog8.parser.ParsingFailedError
class ErrorReporter {
interface IErrorReporter {
fun err(msg: String, position: Position)
fun warn(msg: String, position: Position)
fun noErrors(): Boolean
fun report()
}
internal class ErrorReporter: IErrorReporter {
private enum class MessageSeverity {
WARNING,
ERROR
@ -13,10 +22,14 @@ class ErrorReporter {
private val messages = mutableListOf<CompilerMessage>()
private val alreadyReportedMessages = mutableSetOf<String>()
fun err(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.ERROR, msg, position))
fun warn(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position))
override fun err(msg: String, position: Position) {
messages.add(CompilerMessage(MessageSeverity.ERROR, msg, position))
}
override fun warn(msg: String, position: Position) {
messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position))
}
fun handle() {
override fun report() {
var numErrors = 0
var numWarnings = 0
messages.forEach {
@ -40,5 +53,5 @@ class ErrorReporter {
throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.")
}
fun isEmpty() = messages.isEmpty()
override fun noErrors() = messages.none { it.severity==MessageSeverity.ERROR }
}

View File

@ -0,0 +1,6 @@
package prog8.compiler
interface IStringEncoding {
fun encodeString(str: String, altEncoding: Boolean): List<Short>
fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
}

View File

@ -1,246 +0,0 @@
package prog8.compiler
import prog8.ast.AstToSourceCode
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.statements.Directive
import prog8.compiler.target.C64Target
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.Cx16Target
import prog8.optimizer.*
import prog8.optimizer.UnusedCodeRemover
import prog8.optimizer.constantFold
import prog8.optimizer.optimizeStatements
import prog8.optimizer.simplifyExpressions
import prog8.parser.ModuleImporter
import prog8.parser.ParsingFailedError
import prog8.parser.moduleName
import java.nio.file.Path
import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis
class CompilationResult(val success: Boolean,
val programAst: Program,
val programName: String,
val importedFiles: List<Path>)
fun compileProgram(filepath: Path,
optimize: Boolean,
writeAssembly: Boolean,
slowCodegenWarnings: Boolean,
compilationTarget: String,
outputDir: Path): CompilationResult {
var programName = ""
lateinit var programAst: Program
lateinit var importedFiles: List<Path>
val errors = ErrorReporter()
when(compilationTarget) {
C64Target.name -> CompilationTarget.instance = C64Target
Cx16Target.name -> CompilationTarget.instance = Cx16Target
else -> {
System.err.println("invalid compilation target")
exitProcess(1)
}
}
try {
val totalTime = measureTimeMillis {
// import main module and everything it needs
val (ast, compilationOptions, imported) = parseImports(filepath, errors)
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
programAst = ast
importedFiles = imported
processAst(programAst, errors, compilationOptions)
if (optimize)
optimizeAst(programAst, errors)
postprocessAst(programAst, errors, compilationOptions)
// printAst(programAst)
if(writeAssembly)
programName = writeAssembly(programAst, errors, outputDir, optimize, compilationOptions)
}
System.out.flush()
System.err.flush()
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
return CompilationResult(true, programAst, programName, importedFiles)
} catch (px: ParsingFailedError) {
System.err.print("\u001b[91m") // bright red
System.err.println(px.message)
System.err.print("\u001b[0m") // reset
} catch (ax: AstException) {
System.err.print("\u001b[91m") // bright red
System.err.println(ax.toString())
System.err.print("\u001b[0m") // reset
} catch (x: Exception) {
print("\u001b[91m") // bright red
println("\n* internal error *")
print("\u001b[0m") // reset
System.out.flush()
throw x
} catch (x: NotImplementedError) {
print("\u001b[91m") // bright red
println("\n* internal error: missing feature/code *")
print("\u001b[0m") // reset
System.out.flush()
throw x
}
return CompilationResult(false, Program("failed", mutableListOf()), programName, emptyList())
}
private fun parseImports(filepath: Path, errors: ErrorReporter): Triple<Program, CompilationOptions, List<Path>> {
println("Compiler target: ${CompilationTarget.instance.name}. Parsing...")
val importer = ModuleImporter()
val programAst = Program(moduleName(filepath.fileName), mutableListOf())
importer.importModule(programAst, filepath)
errors.handle()
val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source }
val compilerOptions = determineCompilationOptions(programAst)
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
// depending on the machine and compiler options we may have to include some libraries
CompilationTarget.instance.machine.importLibs(compilerOptions, importer, programAst)
// always import prog8_lib and math
importer.importLibraryModule(programAst, "math")
importer.importLibraryModule(programAst, "prog8_lib")
errors.handle()
return Triple(programAst, compilerOptions, importedFiles)
}
private fun determineCompilationOptions(program: Program): CompilationOptions {
val mainModule = program.modules.first()
val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.toSet()
val floatsEnabled = allOptions.any { it.name == "enable_floats" }
val noSysInit = allOptions.any { it.name == "no_sysinit" }
var zpType: ZeropageType =
if (zpoption == null)
if(floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
else
try {
ZeropageType.valueOf(zpoption)
} catch (x: IllegalArgumentException) {
ZeropageType.KERNALSAFE
// error will be printed by the astchecker
}
if (zpType==ZeropageType.FLOATSAFE && CompilationTarget.instance.name == Cx16Target.name) {
System.err.println("Warning: Cx16 target must use zp option basicsafe instead of floatsafe")
zpType = ZeropageType.BASICSAFE
}
val zpReserved = mainModule.statements
.asSequence()
.filter { it is Directive && it.directive == "%zpreserved" }
.map { (it as Directive).args }
.map { it[0].int!!..it[1].int!! }
.toList()
if(outputType!=null && !OutputType.values().any {it.name==outputType}) {
System.err.println("invalid output type $outputType")
exitProcess(1)
}
if(launcherType!=null && !LauncherType.values().any {it.name==launcherType}) {
System.err.println("invalid launcher type $launcherType")
exitProcess(1)
}
return CompilationOptions(
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
zpType, zpReserved, floatsEnabled, noSysInit
)
}
private fun processAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
// perform initial syntax checks and processings
println("Processing for target ${CompilationTarget.instance.name}...")
programAst.checkIdentifiers(errors)
errors.handle()
programAst.constantFold(errors)
errors.handle()
programAst.reorderStatements(errors)
errors.handle()
programAst.addTypecasts(errors)
errors.handle()
programAst.variousCleanups()
programAst.checkValid(compilerOptions, errors)
errors.handle()
programAst.checkIdentifiers(errors)
errors.handle()
}
private fun optimizeAst(programAst: Program, errors: ErrorReporter) {
// optimize the parse tree
println("Optimizing...")
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = programAst.splitBinaryExpressions()
val optsDone3 = programAst.optimizeStatements(errors)
programAst.constantFold(errors) // because simplified statements and expressions can result in more constants that can be folded away
errors.handle()
if (optsDone1 + optsDone2 + optsDone3 == 0)
break
}
val remover = UnusedCodeRemover(programAst, errors)
remover.visit(programAst)
remover.applyModifications()
errors.handle()
}
private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
programAst.addTypecasts(errors)
errors.handle()
programAst.variousCleanups()
programAst.checkValid(compilerOptions, errors) // check if final tree is still valid
errors.handle()
val callGraph = CallGraph(programAst)
callGraph.checkRecursiveCalls(errors)
errors.handle()
programAst.verifyFunctionArgTypes()
programAst.moveMainAndStartToFirst()
}
private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir: Path,
optimize: Boolean, compilerOptions: CompilationOptions): String {
// asm generation directly from the Ast,
programAst.processAstBeforeAsmGeneration(errors)
errors.handle()
// printAst(programAst)
CompilationTarget.instance.machine.initializeZeropage(compilerOptions)
val assembly = CompilationTarget.instance.asmGenerator(
programAst,
errors,
CompilationTarget.instance.machine.zeropage,
compilerOptions,
outputDir).compileToAssembly(optimize)
assembly.assemble(compilerOptions)
errors.handle()
return assembly.name
}
fun printAst(programAst: Program) {
println()
val printer = AstToSourceCode(::print, programAst)
printer.visit(programAst)
println()
}

View File

@ -0,0 +1,159 @@
package prog8.compiler
import com.github.michaelbull.result.*
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.Position
import prog8.ast.base.SyntaxError
import prog8.ast.statements.Directive
import prog8.ast.statements.DirectiveArg
import prog8.parser.Prog8Parser
import prog8.parser.SourceCode
import java.io.File
import java.nio.file.Path
import kotlin.io.path.*
class ModuleImporter(private val program: Program,
private val compilationTargetName: String,
val errors: IErrorReporter,
sourceDirs: List<String>) {
private val sourcePaths: List<Path> = sourceDirs.map { Path(it) }
fun importModule(filePath: Path): Result<Module, NoSuchFileException> {
val currentDir = Path("").absolute()
val searchIn = listOf(currentDir) + sourcePaths
val candidates = searchIn
.map { it.absolute().div(filePath).normalize().absolute() }
.filter { it.exists() }
.map { currentDir.relativize(it) }
.map { if (it.isAbsolute) it else Path(".", "$it") }
val srcPath = when (candidates.size) {
0 -> return Err(NoSuchFileException(
file = filePath.normalize().toFile(),
reason = "searched in $searchIn"))
1 -> candidates.first()
else -> candidates.first() // when more candiates, pick the one from the first location
}
val logMsg = "importing '${filePath.nameWithoutExtension}' (from file $srcPath)"
println(logMsg)
val module = importModule(SourceCode.File(srcPath))
return if(module==null)
Err(NoSuchFileException(srcPath.toFile()))
else
Ok(module)
}
fun importLibraryModule(name: String): Module? {
val import = Directive("%import", listOf(
DirectiveArg("", name, 42, position = Position("<<<implicit-import>>>", 0, 0, 0))
), Position("<<<implicit-import>>>", 0, 0, 0))
return executeImportDirective(import, null)
}
private fun importModule(src: SourceCode) : Module? {
val moduleAst = Prog8Parser.parseModule(src)
program.addModule(moduleAst)
// accept additional imports
try {
val lines = moduleAst.statements.toMutableList()
lines.asSequence()
.mapIndexed { i, it -> i to it }
.filter { (it.second as? Directive)?.directive == "%import" }
.forEach { executeImportDirective(it.second as Directive, moduleAst) }
moduleAst.statements = lines
return moduleAst
} catch (x: Exception) {
// in case of error, make sure the module we're importing is no longer in the Ast
program.removeModule(moduleAst)
throw x
}
}
private fun executeImportDirective(import: Directive, importingModule: Module?): Module? {
if(import.directive!="%import" || import.args.size!=1)
throw SyntaxError("invalid import directive", import.position)
if(!import.args[0].str.isNullOrEmpty() || import.args[0].name==null)
throw SyntaxError("%import requires unquoted module name", import.position)
val moduleName = import.args[0].name!!
if("$moduleName.p8" == import.position.file)
throw SyntaxError("cannot import self", import.position)
val existing = program.modules.singleOrNull { it.name == moduleName }
if (existing!=null)
return existing
// try internal library first
val moduleResourceSrc = getModuleFromResource("$moduleName.p8", compilationTargetName)
val importedModule =
moduleResourceSrc.fold(
success = {
println("importing '$moduleName' (from internal ${it.origin})")
importModule(it)
},
failure = {
// try filesystem next
val moduleSrc = getModuleFromFile(moduleName, importingModule)
moduleSrc.fold(
success = {
println("importing '$moduleName' (from file ${it.origin})")
importModule(it)
},
failure = {
errors.err("no module found with name $moduleName", import.position)
return null
}
)
}
)
if(importedModule!=null)
removeDirectivesFromImportedModule(importedModule)
return importedModule
}
private fun removeDirectivesFromImportedModule(importedModule: Module) {
// Most global directives don't apply for imported modules, so remove them
val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
var directives = importedModule.statements.filterIsInstance<Directive>()
importedModule.statements.removeAll(directives)
directives = directives.filter{ it.directive !in moduleLevelDirectives }
importedModule.statements.addAll(0, directives)
}
private fun getModuleFromResource(name: String, compilationTargetName: String): Result<SourceCode, NoSuchFileException> {
val result =
runCatching { SourceCode.Resource("/prog8lib/$compilationTargetName/$name") }
.orElse { runCatching { SourceCode.Resource("/prog8lib/$name") } }
return result.mapError { NoSuchFileException(File(name)) }
}
private fun getModuleFromFile(name: String, importingModule: Module?): Result<SourceCode, NoSuchFileException> {
val fileName = "$name.p8"
val locations =
if (importingModule == null) { // <=> imported from library module
sourcePaths
} else {
val dropCurDir = if(sourcePaths.isNotEmpty() && sourcePaths[0].name == ".") 1 else 0
sourcePaths.drop(dropCurDir) +
// TODO: won't work until Prog8Parser is fixed s.t. it fully initializes the modules it returns. // hm, what won't work?)
listOf(Path(importingModule.position.file).parent ?: Path("")) +
listOf(Path(".", "prog8lib"))
}
locations.forEach {
try {
return Ok(SourceCode.File(it.resolve(fileName)))
} catch (e: NoSuchFileException) {
}
}
return Err(NoSuchFileException(File("name")))
}
}

View File

@ -19,9 +19,31 @@ abstract class Zeropage(protected val options: CompilationOptions) {
val allowedDatatypes = NumericDatatypes
fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
fun availableBytes() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
fun hasByteAvailable() = if(options.zeropage==ZeropageType.DONTUSE) false else free.isNotEmpty()
fun availableWords(): Int {
if(options.zeropage==ZeropageType.DONTUSE)
return 0
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: ErrorReporter): Int {
val words = free.windowed(2).filter { it[0] == it[1]-1 }
var nonOverlappingWordsCount = 0
var prevMsbLoc = -1
for(w in words) {
if(w[0]!=prevMsbLoc) {
nonOverlappingWordsCount++
prevMsbLoc = w[1]
}
}
return nonOverlappingWordsCount
}
fun hasWordAvailable(): Boolean {
if(options.zeropage==ZeropageType.DONTUSE)
return false
return free.windowed(2).any { it[0] == it[1] - 1 }
}
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: IErrorReporter): Int {
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"}
if(options.zeropage==ZeropageType.DONTUSE)

View File

@ -0,0 +1,211 @@
package prog8.compiler.astprocessing
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Directive
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.BeforeAsmGenerationAstChanger
import prog8.compiler.CompilationOptions
import prog8.compiler.IErrorReporter
import prog8.compiler.IStringEncoding
import prog8.compiler.target.ICompilationTarget
import prog8.compiler.target.IMachineDefinition
import kotlin.math.abs
fun RangeExpr.size(encoding: IStringEncoding): Int? {
val fromLv = (from as? NumericLiteralValue)
val toLv = (to as? NumericLiteralValue)
if(fromLv==null || toLv==null)
return null
return toConstantIntegerRange(encoding)?.count()
}
fun RangeExpr.toConstantIntegerRange(encoding: IStringEncoding): IntProgression? {
val fromVal: Int
val toVal: Int
val fromString = from as? StringLiteralValue
val toString = to as? StringLiteralValue
if(fromString!=null && toString!=null ) {
// string range -> int range over character values
fromVal = encoding.encodeString(fromString.value, fromString.altEncoding)[0].toInt()
toVal = encoding.encodeString(toString.value, fromString.altEncoding)[0].toInt()
} else {
val fromLv = from as? NumericLiteralValue
val toLv = to as? NumericLiteralValue
if(fromLv==null || toLv==null)
return null // non-constant range
// integer range
fromVal = fromLv.number.toInt()
toVal = toLv.number.toInt()
}
val stepVal = (step as? NumericLiteralValue)?.number?.toInt() ?: 1
return makeRange(fromVal, toVal, stepVal)
}
private fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
return when {
fromVal <= toVal -> when {
stepVal <= 0 -> IntRange.EMPTY
stepVal == 1 -> fromVal..toVal
else -> fromVal..toVal step stepVal
}
else -> when {
stepVal >= 0 -> IntRange.EMPTY
stepVal == -1 -> fromVal downTo toVal
else -> fromVal downTo toVal step abs(stepVal)
}
}
}
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: IErrorReporter, compTarget: ICompilationTarget) {
val checker = AstChecker(this, compilerOptions, errors, compTarget)
checker.visit(this)
}
internal fun Program.processAstBeforeAsmGeneration(errors: IErrorReporter, compTarget: ICompilationTarget) {
val fixer = BeforeAsmGenerationAstChanger(this, errors, compTarget)
fixer.visit(this)
while(errors.noErrors() && fixer.applyModifications()>0) {
fixer.visit(this)
}
}
internal fun Program.reorderStatements(errors: IErrorReporter) {
val reorder = StatementReorderer(this, errors)
reorder.visit(this)
if(errors.noErrors()) {
reorder.applyModifications()
reorder.visit(this)
if(errors.noErrors())
reorder.applyModifications()
}
}
internal fun Program.charLiteralsToUByteLiterals(errors: IErrorReporter, enc: IStringEncoding) {
val walker = object : AstWalker() {
override fun after(char: CharLiteral, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.ReplaceNode(
char,
NumericLiteralValue(DataType.UBYTE, enc.encodeString(char.value.toString(), char.altEncoding)[0].toInt(), char.position),
parent
))
}
}
walker.visit(this)
walker.applyModifications()
}
internal fun Program.addTypecasts(errors: IErrorReporter) {
val caster = TypecastsAdder(this, errors)
caster.visit(this)
caster.applyModifications()
}
internal fun Program.verifyFunctionArgTypes() {
val fixer = VerifyFunctionArgTypes(this)
fixer.visit(this)
}
internal fun Program.checkIdentifiers(errors: IErrorReporter, options: CompilationOptions) {
val checker2 = AstIdentifiersChecker(this, errors, options.compTarget)
checker2.visit(this)
if(errors.noErrors()) {
val transforms = AstVariousTransforms(this)
transforms.visit(this)
transforms.applyModifications()
val lit2decl = LiteralsToAutoVars(this)
lit2decl.visit(this)
lit2decl.applyModifications()
}
}
internal fun Program.variousCleanups(program: Program, errors: IErrorReporter) {
val process = VariousCleanups(program, errors)
process.visit(this)
if(errors.noErrors())
process.applyModifications()
}
internal fun Program.moveMainAndStartToFirst() {
// the module containing the program entrypoint is moved to the first in the sequence.
// the "main" block containing the entrypoint is moved to the top in there,
// and finally the entrypoint subroutine "start" itself is moved to the top in that block.
val directives = modules[0].statements.filterIsInstance<Directive>()
val start = this.entrypoint
val mod = start.definingModule
val block = start.definingBlock
moveModuleToFront(mod)
mod.remove(block)
var afterDirective = mod.statements.indexOfFirst { it !is Directive }
if(afterDirective<0)
mod.statements.add(block)
else
mod.statements.add(afterDirective, block)
block.remove(start)
afterDirective = block.statements.indexOfFirst { it !is Directive }
if(afterDirective<0)
block.statements.add(start)
else
block.statements.add(afterDirective, start)
// overwrite the directives in the module containing the entrypoint
for(directive in directives) {
modules[0].statements.removeAll { it is Directive && it.directive == directive.directive }
modules[0].statements.add(0, directive)
}
}
internal fun AssignTarget.isInRegularRAMof(machine: IMachineDefinition): Boolean {
val memAddr = memoryAddress
val arrayIdx = arrayindexed
val ident = identifier
when {
memAddr != null -> {
return when (memAddr.addressExpression) {
is NumericLiteralValue -> {
machine.isRegularRAMaddress((memAddr.addressExpression as NumericLiteralValue).number.toInt())
}
is IdentifierReference -> {
val program = definingModule.program
val decl = (memAddr.addressExpression as IdentifierReference).targetVarDecl(program)
if ((decl?.type == VarDeclType.VAR || decl?.type == VarDeclType.CONST) && decl.value is NumericLiteralValue)
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else
false
}
else -> false
}
}
arrayIdx != null -> {
val program = definingModule.program
val targetStmt = arrayIdx.arrayvar.targetVarDecl(program)
return if (targetStmt?.type == VarDeclType.MEMORY) {
val addr = targetStmt.value as? NumericLiteralValue
if (addr != null)
machine.isRegularRAMaddress(addr.number.toInt())
else
false
} else true
}
ident != null -> {
val program = definingModule.program
val decl = ident.targetVarDecl(program)!!
return if (decl.type == VarDeclType.MEMORY && decl.value is NumericLiteralValue)
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else
true
}
else -> return true
}
}

View File

@ -1,95 +1,61 @@
package prog8.ast.processing
package prog8.compiler.astprocessing
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.base.Position
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.*
import prog8.compiler.target.CompilationTarget
import prog8.functions.BuiltinFunctions
import prog8.ast.walk.IAstVisitor
import prog8.compiler.IErrorReporter
import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.target.ICompilationTarget
internal class AstIdentifiersChecker(private val program: Program, private val errors: ErrorReporter) : IAstVisitor {
internal class AstIdentifiersChecker(private val program: Program, private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : IAstVisitor {
private var blocks = mutableMapOf<String, Block>()
private fun nameError(name: String, position: Position, existing: Statement) {
errors.err("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position)
}
override fun visit(module: Module) {
blocks.clear() // blocks may be redefined within a different module
super.visit(module)
}
override fun visit(block: Block) {
if(block.name in CompilationTarget.instance.machine.opcodeNames)
if(block.name in compTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${block.name}'", block.position)
val existing = blocks[block.name]
if(existing!=null)
nameError(block.name, block.position, existing)
if(existing!=null) {
if(block.isInLibrary)
nameError(existing.name, existing.position, block)
else
nameError(block.name, block.position, existing)
}
else
blocks[block.name] = block
super.visit(block)
}
override fun visit(directive: Directive) {
if(directive.directive=="%target") {
val compatibleTarget = directive.args.single().name
if (compatibleTarget != CompilationTarget.instance.name)
errors.err("module's compilation target ($compatibleTarget) differs from active target (${CompilationTarget.instance.name})", directive.position)
}
super.visit(directive)
}
override fun visit(decl: VarDecl) {
decl.datatypeErrors.forEach { errors.err(it.message, it.position) }
if(decl.name in BuiltinFunctions)
errors.err("builtin function cannot be redefined", decl.position)
if(decl.name in CompilationTarget.instance.machine.opcodeNames)
if(decl.name in compTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
if(decl.datatype==DataType.STRUCT) {
if (decl.structHasBeenFlattened)
return super.visit(decl) // don't do this multiple times
if (decl.struct == null) {
errors.err("undefined struct type", decl.position)
return super.visit(decl)
}
if (decl.struct!!.statements.any { (it as VarDecl).datatype !in NumericDatatypes })
return super.visit(decl) // a non-numeric member, not supported. proper error is given by AstChecker later
if (decl.value is NumericLiteralValue) {
errors.err("you cannot initialize a struct using a single value", decl.position)
return super.visit(decl)
}
if (decl.value != null && decl.value !is ArrayLiteralValue) {
errors.err("initializing a struct requires array literal value", decl.value?.position ?: decl.position)
return super.visit(decl)
}
}
val existing = program.namespace.lookup(listOf(decl.name), decl)
if (existing != null && existing !== decl)
nameError(decl.name, decl.position, existing)
if(decl.definingBlock().name==decl.name)
nameError(decl.name, decl.position, decl.definingBlock())
if(decl.definingSubroutine()?.name==decl.name)
nameError(decl.name, decl.position, decl.definingSubroutine()!!)
if(decl.definingBlock.name==decl.name)
nameError(decl.name, decl.position, decl.definingBlock)
if(decl.definingSubroutine?.name==decl.name)
nameError(decl.name, decl.position, decl.definingSubroutine!!)
super.visit(decl)
}
override fun visit(subroutine: Subroutine) {
if(subroutine.name in CompilationTarget.instance.machine.opcodeNames) {
if(subroutine.name in compTarget.machine.opcodeNames) {
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
} else if(subroutine.name in BuiltinFunctions) {
// the builtin functions can't be redefined
@ -103,8 +69,8 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
if (existing != null && existing !== subroutine)
nameError(subroutine.name, subroutine.position, existing)
// check that there are no local variables, labels, or other subs that redefine the subroutine's parameters
val symbolsInSub = subroutine.allDefinedSymbols()
// check that there are no local variables, labels, or other subs that redefine the subroutine's parameters. Blocks are okay.
val symbolsInSub = subroutine.allDefinedSymbols
val namesInSub = symbolsInSub.map{ it.first }.toSet()
val paramNames = subroutine.parameters.map { it.name }.toSet()
val paramsToCheck = paramNames.intersect(namesInSub)
@ -112,28 +78,34 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
val labelOrVar = subroutine.getLabelOrVariable(name)
if(labelOrVar!=null && labelOrVar.position != subroutine.position)
nameError(name, labelOrVar.position, subroutine)
val sub = subroutine.statements.singleOrNull { it is Subroutine && it.name==name}
val sub = subroutine.statements.firstOrNull { it is Subroutine && it.name==name}
if(sub!=null)
nameError(name, sub.position, subroutine)
nameError(name, subroutine.position, sub)
}
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
}
if(subroutine.name == subroutine.definingBlock.name) {
// subroutines cannot have the same name as their enclosing block,
// because this causes symbol scoping issues in the resulting assembly source
nameError(subroutine.name, subroutine.position, subroutine.definingBlock)
}
}
super.visit(subroutine)
}
override fun visit(label: Label) {
if(label.name in CompilationTarget.instance.machine.opcodeNames)
if(label.name in compTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position)
if(label.name in BuiltinFunctions) {
// the builtin functions can't be redefined
errors.err("builtin function cannot be redefined", label.position)
} else {
val existing = label.definingSubroutine()?.getAllLabels(label.name) ?: emptyList()
val existing = label.definingSubroutine?.getAllLabels(label.name) ?: emptyList()
for(el in existing) {
if(el === label || el.name != label.name)
continue
@ -153,14 +125,4 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
super.visit(string)
}
override fun visit(structDecl: StructDecl) {
for(member in structDecl.statements){
val decl = member as? VarDecl
if(decl!=null && decl.datatype !in NumericDatatypes)
errors.err("structs can only contain numerical types", decl.position)
}
super.visit(structDecl)
}
}

View File

@ -1,34 +1,23 @@
package prog8.ast.processing
package prog8.compiler.astprocessing
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.base.DataType
import prog8.ast.expressions.ArrayIndexedExpression
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.DirectMemoryRead
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
internal class AstVariousTransforms(private val program: Program) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// is it a struct variable? then define all its struct members as mangled names,
// and include the original decl as well.
if(decl.datatype==DataType.STRUCT && !decl.structHasBeenFlattened) {
val decls = decl.flattenStructMembers()
decls.add(decl)
val result = AnonymousScope(decls, decl.position)
return listOf(IAstModification.ReplaceNode(
decl, result, parent
))
}
return noModifications
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
// For non-kernel subroutines and non-asm parameters:
// For non-kernal subroutines and non-asm parameters:
// inject subroutine params as local variables (if they're not there yet).
val symbolsInSub = subroutine.allDefinedSymbols()
val symbolsInSub = subroutine.allDefinedSymbols
val namesInSub = symbolsInSub.map{ it.first }.toSet()
if(subroutine.asmAddress==null) {
if(subroutine.asmParameterRegisters.isEmpty() && subroutine.parameters.isNotEmpty()) {
@ -77,6 +66,10 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
return noModifications
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
return replacePointerVarIndexWithMemread(program, arrayIndexedExpression, parent)
}
private fun concatString(expr: BinaryExpression): StringLiteralValue? {
val rightStrval = expr.right as? StringLiteralValue
val leftStrval = expr.left as? StringLiteralValue
@ -103,3 +96,25 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
}
}
}
internal fun replacePointerVarIndexWithMemread(program: Program, arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
// rewrite pointervar[index] into @(pointervar+index)
val indexer = arrayIndexedExpression.indexer
val add = BinaryExpression(arrayIndexedExpression.arrayvar, "+", indexer.indexExpr, arrayIndexedExpression.position)
return if(parent is AssignTarget) {
// we're part of the target of an assignment, we have to actually change the assign target itself
val memwrite = DirectMemoryWrite(add, arrayIndexedExpression.position)
val newtarget = AssignTarget(null, null, memwrite, arrayIndexedExpression.position)
listOf(IAstModification.ReplaceNode(parent, newtarget, parent.parent))
} else {
val memread = DirectMemoryRead(add, arrayIndexedExpression.position)
listOf(IAstModification.ReplaceNode(arrayIndexedExpression, memread, parent))
}
}
return emptyList()
}

View File

@ -1,24 +1,25 @@
package prog8.ast.processing
package prog8.compiler.astprocessing
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.base.DataType
import prog8.ast.expressions.ArrayLiteralValue
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.VarDecl
import prog8.ast.statements.WhenChoice
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
if(string.parent !is VarDecl && string.parent !is WhenChoice) {
// replace the literal string by a identifier reference to a new local vardecl
val vardecl = VarDecl.createAuto(string)
val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position)
return listOf(
IAstModification.ReplaceNode(string, identifier, parent),
IAstModification.InsertFirst(vardecl, string.definingScope())
)
// replace the literal string by an identifier reference to the interned string
val scopedName = program.internString(string)
val identifier = IdentifierReference(scopedName, string.position)
return listOf(IAstModification.ReplaceNode(string, identifier, parent))
}
return noModifications
}
@ -28,7 +29,7 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
if(vardecl!=null) {
// adjust the datatype of the array (to an educated guess)
val arrayDt = array.type
if(!arrayDt.istype(vardecl.datatype)) {
if(arrayDt isnot vardecl.datatype) {
val cast = array.cast(vardecl.datatype)
if (cast != null && cast !== array)
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
@ -37,13 +38,13 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
val arrayDt = array.guessDatatype(program)
if(arrayDt.isKnown) {
// this array literal is part of an expression, turn it into an identifier reference
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
val litval2 = array.cast(arrayDt.getOr(DataType.UNDEFINED))
if(litval2!=null) {
val vardecl2 = VarDecl.createAuto(litval2)
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
return listOf(
IAstModification.ReplaceNode(array, identifier, parent),
IAstModification.InsertFirst(vardecl2, array.definingScope())
IAstModification.InsertFirst(vardecl2, array.definingScope)
)
}
}

View File

@ -1,4 +1,4 @@
package prog8.ast.processing
package prog8.compiler.astprocessing
/*

View File

@ -0,0 +1,289 @@
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.functions.BuiltinFunctions
internal class StatementReorderer(val program: Program, val errors: IErrorReporter) : AstWalker() {
// Reorders the statements in a way the compiler needs.
// - 'main' block must be the very first statement UNLESS it has an address set.
// - library blocks are put last.
// - blocks are ordered by address, where blocks without address are placed last.
// - in every block and module, most directives and vardecls are moved to the top. (not in subroutines!)
// - the 'start' subroutine is moved to the top.
// - (syntax desugaring) a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement.
// - in-place assignments are reordered a bit so that they are mostly of the form A = A <operator> <rest>
// - sorts the choices in when statement.
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc.).
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
val (blocks, other) = module.statements.partition { it is Block }
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: Int.MAX_VALUE }).toMutableList()
val mainBlock = module.statements.filterIsInstance<Block>().firstOrNull { it.name=="main" }
if(mainBlock!=null && mainBlock.address==null) {
module.statements.remove(mainBlock)
module.statements.add(0, mainBlock)
}
reorderVardeclsAndDirectives(module.statements)
return noModifications
}
private fun reorderVardeclsAndDirectives(statements: MutableList<Statement>) {
val varDecls = statements.filterIsInstance<VarDecl>()
statements.removeAll(varDecls)
statements.addAll(0, varDecls)
val directives = statements.filterIsInstance<Directive>().filter {it.directive in directivesToMove}
statements.removeAll(directives)
statements.addAll(0, directives)
}
override fun before(block: Block, parent: Node): Iterable<IAstModification> {
parent as Module
if(block.isInLibrary) {
return listOf(
IAstModification.Remove(block, parent),
IAstModification.InsertLast(block, parent)
)
}
reorderVardeclsAndDirectives(block.statements)
return noModifications
}
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
if(subroutine.name=="start" && parent is Block) {
if(parent.statements.filterIsInstance<Subroutine>().first().name!="start") {
return listOf(
IAstModification.Remove(subroutine, parent),
IAstModification.InsertFirst(subroutine, parent)
)
}
}
val subs = subroutine.statements.filterIsInstance<Subroutine>()
if(subs.isNotEmpty()) {
// all subroutines defined within this subroutine are moved to the end
return subs.map { IAstModification.Remove(it, subroutine) } +
subs.map { IAstModification.InsertLast(it, subroutine) }
}
return noModifications
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
return replacePointerVarIndexWithMemread(program, arrayIndexedExpression, parent)
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
// ConstValue <associativeoperator> X --> X <associativeoperator> ConstValue
// (this should be done by the ExpressionSimplifier when optimizing is enabled,
// but the current assembly code generator for IF statements now also depends on it, so we do it here regardless of optimization.)
if (expr.left.constValue(program) != null && expr.operator in associativeOperators && expr.right.constValue(program) == null)
return listOf(IAstModification.SwapOperands(expr))
// when using a simple bit shift and assigning it to a variable of a different type,
// try to make the bit shifting 'wide enough' to fall into the variable's type.
// with this, for instance, uword x = 1 << 10 will result in 1024 rather than 0 (the ubyte result).
if(expr.operator=="<<" || expr.operator==">>") {
val leftDt = expr.left.inferType(program)
when (parent) {
is Assignment -> {
val targetDt = parent.target.inferType(program)
if(leftDt != targetDt) {
val cast = TypecastExpression(expr.left, targetDt.getOr(DataType.UNDEFINED), true, parent.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
}
is VarDecl -> {
if(leftDt isnot parent.datatype) {
val cast = TypecastExpression(expr.left, parent.datatype, true, parent.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
}
is IFunctionCall -> {
val argnum = parent.args.indexOf(expr)
when (val callee = parent.target.targetStatement(program)) {
is Subroutine -> {
val paramType = callee.parameters[argnum].type
if(leftDt isAssignableTo paramType) {
val cast = TypecastExpression(expr.left, paramType, true, parent.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
}
is BuiltinFunctionStatementPlaceholder -> {
val func = BuiltinFunctions.getValue(callee.name)
val paramTypes = func.parameters[argnum].possibleDatatypes
for(type in paramTypes) {
if(leftDt isAssignableTo type) {
val cast = TypecastExpression(expr.left, type, true, parent.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
}
}
else -> throw FatalAstException("weird callee")
}
}
else -> return noModifications
}
}
else if(expr.operator in logicalOperators) {
// make sure that logical expressions like "var and other-logical-expression
// is rewritten as "var!=0 and other-logical-expression", to avoid bitwise boolean and
// generating the wrong results later
fun wrapped(expr: Expression): Expression =
BinaryExpression(expr, "!=", NumericLiteralValue(DataType.UBYTE, 0, expr.position), expr.position)
fun isLogicalExpr(expr: Expression?): Boolean {
if(expr is BinaryExpression && expr.operator in (logicalOperators + comparisonOperators))
return true
if(expr is PrefixExpression && expr.operator in logicalOperators)
return true
return false
}
return if(isLogicalExpr(expr.left)) {
if(isLogicalExpr(expr.right))
noModifications
else
listOf(IAstModification.ReplaceNode(expr.right, wrapped(expr.right), expr))
} else {
if(isLogicalExpr(expr.right))
listOf(IAstModification.ReplaceNode(expr.left, wrapped(expr.left), expr))
else {
listOf(
IAstModification.ReplaceNode(expr.left, wrapped(expr.left), expr),
IAstModification.ReplaceNode(expr.right, wrapped(expr.right), expr)
)
}
}
}
return noModifications
}
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
val choices = whenStatement.choiceValues(program).sortedBy {
it.first?.first() ?: Int.MAX_VALUE
}
whenStatement.choices.clear()
choices.mapTo(whenStatement.choices) { it.second }
return noModifications
}
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
val valueType = assignment.value.inferType(program)
val targetType = assignment.target.inferType(program)
if(targetType.isArray && valueType.isArray) {
if (assignment.value is ArrayLiteralValue) {
errors.err("cannot assign array literal here, use separate assignment per element", assignment.position)
} else {
return copyArrayValue(assignment)
}
}
return noModifications
}
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
// rewrite in-place assignment expressions a bit so that the assignment target usually is the leftmost operand
val binExpr = assignment.value as? BinaryExpression
if(binExpr!=null) {
if(binExpr.left isSameAs assignment.target) {
// A = A <operator> 5, unchanged
return noModifications
}
if(binExpr.operator in associativeOperators) {
if (binExpr.right isSameAs assignment.target) {
// A = v <associative-operator> A ==> A = A <associative-operator> v
return listOf(IAstModification.SwapOperands(binExpr))
}
val leftBinExpr = binExpr.left as? BinaryExpression
if(leftBinExpr?.operator == binExpr.operator) {
return if(leftBinExpr.left isSameAs assignment.target) {
// A = (A <associative-operator> x) <same-operator> y ==> A = A <associative-operator> (x <same-operator> y)
val newRight = BinaryExpression(leftBinExpr.right, binExpr.operator, binExpr.right, binExpr.position)
val newValue = BinaryExpression(leftBinExpr.left, binExpr.operator, newRight, binExpr.position)
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
} else {
// A = (x <associative-operator> A) <same-operator> y ==> A = A <associative-operator> (x <same-operator> y)
val newRight = BinaryExpression(leftBinExpr.left, binExpr.operator, binExpr.right, binExpr.position)
val newValue = BinaryExpression(leftBinExpr.right, binExpr.operator, newRight, binExpr.position)
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
}
}
val rightBinExpr = binExpr.right as? BinaryExpression
if(rightBinExpr?.operator == binExpr.operator) {
return if(rightBinExpr.left isSameAs assignment.target) {
// A = x <associative-operator> (A <same-operator> y) ==> A = A <associative-operator> (x <same-operator> y)
val newRight = BinaryExpression(binExpr.left, binExpr.operator, rightBinExpr.right, binExpr.position)
val newValue = BinaryExpression(rightBinExpr.left, binExpr.operator, newRight, binExpr.position)
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
} else {
// A = x <associative-operator> (y <same-operator> A) ==> A = A <associative-operator> (x <same-operator> y)
val newRight = BinaryExpression(binExpr.left, binExpr.operator, rightBinExpr.left, binExpr.position)
val newValue = BinaryExpression(rightBinExpr.right, binExpr.operator, newRight, binExpr.position)
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
}
}
}
}
return noModifications
}
private fun copyArrayValue(assign: Assignment): List<IAstModification> {
val identifier = assign.target.identifier!!
val targetVar = identifier.targetVarDecl(program)!!
if(targetVar.arraysize==null)
errors.err("array has no defined size", assign.position)
if(assign.value !is IdentifierReference) {
errors.err("invalid array value to assign to other array", assign.value.position)
return noModifications
}
val sourceIdent = assign.value as IdentifierReference
val sourceVar = sourceIdent.targetVarDecl(program)!!
if(!sourceVar.isArray) {
errors.err("value must be an array", sourceIdent.position)
} else {
if (sourceVar.arraysize!!.constIndex() != targetVar.arraysize!!.constIndex())
errors.err("element count mismatch", assign.position)
if (sourceVar.datatype != targetVar.datatype)
errors.err("element type mismatch", assign.position)
}
if(!errors.noErrors())
return noModifications
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), assign.position),
mutableListOf(
AddressOf(sourceIdent, assign.position),
AddressOf(identifier, assign.position),
NumericLiteralValue.optimalInteger(targetVar.arraysize!!.constIndex()!!, assign.position)
),
true,
assign.position
)
return listOf(IAstModification.ReplaceNode(assign, memcopy, assign.parent))
}
}

View File

@ -1,31 +1,35 @@
package prog8.ast.processing
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.functions.BuiltinFunctions
class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalker() {
class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalker() {
/*
* Make sure any value assignments get the proper type casts if needed to cast them into the target variable's type.
* (this includes function call arguments)
*/
private val noModifications = emptyList<IAstModification>()
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val declValue = decl.value
if(decl.type==VarDeclType.VAR && declValue!=null && decl.struct==null) {
if(decl.type==VarDeclType.VAR && declValue!=null) {
val valueDt = declValue.inferType(program)
if(!valueDt.istype(decl.datatype)) {
if(valueDt isnot decl.datatype) {
// don't add a typecast on an array initializer value
if(valueDt.typeOrElse(DataType.STRUCT) in IntegerDatatypes && decl.datatype in ArrayDatatypes)
if(valueDt.isInteger && decl.datatype in ArrayDatatypes)
return noModifications
// don't add a typecast if the initializer value is inherently not assignable
if(valueDt isNotAssignableTo decl.datatype)
return noModifications
return listOf(IAstModification.ReplaceNode(
@ -43,7 +47,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
val rightDt = expr.right.inferType(program)
if(leftDt.isKnown && rightDt.isKnown && leftDt!=rightDt) {
// determine common datatype and add typecast as required to make left and right equal types
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.typeOrElse(DataType.STRUCT), rightDt.typeOrElse(DataType.STRUCT), expr.left, expr.right)
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.getOr(DataType.UNDEFINED), rightDt.getOr(DataType.UNDEFINED), expr.left, expr.right)
if(toFix!=null) {
return when {
toFix===expr.left -> listOf(IAstModification.ReplaceNode(
@ -62,8 +66,8 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
val valueItype = assignment.value.inferType(program)
val targetItype = assignment.target.inferType(program)
if(targetItype.isKnown && valueItype.isKnown) {
val targettype = targetItype.typeOrElse(DataType.STRUCT)
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
val targettype = targetItype.getOr(DataType.UNDEFINED)
val valuetype = valueItype.getOr(DataType.UNDEFINED)
if (valuetype != targettype) {
if (valuetype isAssignableTo targettype) {
if(valuetype in IterableDatatypes && targettype==DataType.UWORD)
@ -106,23 +110,23 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
return afterFunctionCallArgs(functionCallStatement, functionCallStatement.definingScope())
return afterFunctionCallArgs(functionCallStatement)
}
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
return afterFunctionCallArgs(functionCall, functionCall.definingScope())
return afterFunctionCallArgs(functionCall)
}
private fun afterFunctionCallArgs(call: IFunctionCall, scope: INameScope): Iterable<IAstModification> {
private fun afterFunctionCallArgs(call: IFunctionCall): Iterable<IAstModification> {
// see if a typecast is needed to convert the arguments into the required parameter's type
val modifications = mutableListOf<IAstModification>()
when(val sub = call.target.targetStatement(scope)) {
when(val sub = call.target.targetStatement(program)) {
is Subroutine -> {
sub.parameters.zip(call.args).forEachIndexed { index, pair ->
val argItype = pair.second.inferType(program)
if(argItype.isKnown) {
val argtype = argItype.typeOrElse(DataType.STRUCT)
val argtype = argItype.getOr(DataType.UNDEFINED)
val requiredType = pair.first.type
if (requiredType != argtype) {
if (argtype isAssignableTo requiredType) {
@ -155,7 +159,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
func.parameters.zip(call.args).forEachIndexed { index, pair ->
val argItype = pair.second.inferType(program)
if (argItype.isKnown) {
val argtype = argItype.typeOrElse(DataType.STRUCT)
val argtype = argItype.getOr(DataType.UNDEFINED)
if (pair.first.possibleDatatypes.all { argtype != it }) {
for (possibleType in pair.first.possibleDatatypes) {
if (argtype isAssignableTo possibleType) {
@ -178,7 +182,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
// warn about any implicit type casts to Float, because that may not be intended
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
if(typecast.implicit && typecast.type.oneOf(DataType.FLOAT, DataType.ARRAY_F)) {
errors.warn("integer implicitly converted to float. Suggestion: use float literals, add an explicit cast, or revert to integer arithmetic", typecast.position)
}
return noModifications
@ -187,7 +191,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
override fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
// make sure the memory address is an uword
val dt = memread.addressExpression.inferType(program)
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
if(dt.isKnown && dt.getOr(DataType.UWORD)!=DataType.UWORD) {
val typecast = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread))
@ -198,7 +202,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
override fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> {
// make sure the memory address is an uword
val dt = memwrite.addressExpression.inferType(program)
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
if(dt.isKnown && dt.getOr(DataType.UWORD)!=DataType.UWORD) {
val typecast = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite))
@ -210,10 +214,10 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
// add a typecast to the return type if it doesn't match the subroutine's signature
val returnValue = returnStmt.value
if(returnValue!=null) {
val subroutine = returnStmt.definingSubroutine()!!
val subroutine = returnStmt.definingSubroutine!!
if(subroutine.returntypes.size==1) {
val subReturnType = subroutine.returntypes.first()
if (returnValue.inferType(program).istype(subReturnType))
if (returnValue.inferType(program) istype subReturnType)
return noModifications
if (returnValue is NumericLiteralValue) {
val cast = returnValue.cast(subroutine.returntypes.single())

View File

@ -0,0 +1,121 @@
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.base.Position
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
internal class VariousCleanups(val program: Program, val errors: IErrorReporter): AstWalker() {
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.Remove(nopStatement, parent as INameScope))
}
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
return if(parent is INameScope)
listOf(ScopeFlatten(scope, parent as INameScope))
else
noModifications
}
class ScopeFlatten(val scope: AnonymousScope, val into: INameScope) : IAstModification {
override fun perform() {
val idx = into.statements.indexOf(scope)
if(idx>=0) {
into.statements.addAll(idx+1, scope.statements)
scope.statements.forEach { it.parent = into as Node }
into.statements.remove(scope)
}
}
}
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
return before(functionCallStatement as IFunctionCall, parent, functionCallStatement.position)
}
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
return before(functionCall as IFunctionCall, parent, functionCall.position)
}
private fun before(functionCall: IFunctionCall, parent: Node, position: Position): Iterable<IAstModification> {
if(functionCall.target.nameInSource==listOf("peek")) {
// peek(a) is synonymous with @(a)
val memread = DirectMemoryRead(functionCall.args.single(), position)
return listOf(IAstModification.ReplaceNode(functionCall as Node, memread, parent))
}
if(functionCall.target.nameInSource==listOf("poke")) {
// poke(a, v) is synonymous with @(a) = v
val tgt = AssignTarget(null, null, DirectMemoryWrite(functionCall.args[0], position), position)
val assign = Assignment(tgt, functionCall.args[1], position)
return listOf(IAstModification.ReplaceNode(functionCall as Node, assign, parent))
}
return noModifications
}
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
if(typecast.parent!==parent)
throw FatalAstException("parent node mismatch at $typecast")
if(typecast.expression is NumericLiteralValue) {
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type)
if(value.isValid)
return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent))
}
val sourceDt = typecast.expression.inferType(program)
if(sourceDt istype typecast.type)
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
return noModifications
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
if(subroutine.parent!==parent)
throw FatalAstException("parent node mismatch at $subroutine")
return noModifications
}
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.parent!==parent)
throw FatalAstException("parent node mismatch at $assignment")
return noModifications
}
override fun after(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> {
if(assignTarget.parent!==parent)
throw FatalAstException("parent node mismatch at $assignTarget")
return noModifications
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.parent!==parent)
throw FatalAstException("parent node mismatch at $decl")
return noModifications
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
if(scope.parent!==parent)
throw FatalAstException("parent node mismatch at $scope")
return noModifications
}
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
if(returnStmt.parent!==parent)
throw FatalAstException("parent node mismatch at $returnStmt")
return noModifications
}
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
if(identifier.parent!==parent)
throw FatalAstException("parent node mismatch at $identifier")
return noModifications
}
}

View File

@ -1,26 +1,26 @@
package prog8.ast.processing
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.Expression
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.compiler.CompilerException
import prog8.functions.BuiltinFunctions
import prog8.compiler.functions.BuiltinFunctions
class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
override fun visit(functionCall: FunctionCall) {
val error = checkTypes(functionCall as IFunctionCall, functionCall.definingScope(), program)
val error = checkTypes(functionCall as IFunctionCall, program)
if(error!=null)
throw CompilerException(error)
}
override fun visit(functionCallStatement: FunctionCallStatement) {
val error = checkTypes(functionCallStatement as IFunctionCall, functionCallStatement.definingScope(), program)
val error = checkTypes(functionCallStatement as IFunctionCall, program)
if (error!=null)
throw CompilerException(error)
}
@ -39,12 +39,13 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
return false
}
fun checkTypes(call: IFunctionCall, scope: INameScope, program: Program): String? {
fun checkTypes(call: IFunctionCall, program: Program): String? {
val argITypes = call.args.map { it.inferType(program) }
if(argITypes.any { !it.isKnown })
throw FatalAstException("unknown dt")
val argtypes = argITypes.map { it.typeOrElse(DataType.STRUCT) }
val target = call.target.targetStatement(scope)
val firstUnknownDt = argITypes.indexOfFirst { it.isUnknown }
if(firstUnknownDt>=0)
return "argument ${firstUnknownDt+1} invalid argument type"
val argtypes = argITypes.map { it.getOr(DataType.UNDEFINED) }
val target = call.target.targetStatement(program)
if (target is Subroutine) {
if(call.args.size != target.parameters.size)
return "invalid number of arguments"
@ -60,8 +61,15 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
// multiple return values will NOT work inside an expression.
// they MIGHT work in a regular assignment or just a function call statement.
val parent = if(call is Statement) call.parent else if(call is Expression) call.parent else null
if(call !is FunctionCallStatement && parent !is Assignment && parent !is VarDecl) {
return "can't use subroutine call that returns multiple return values here (try moving it into a separate assignment)"
if (call !is FunctionCallStatement) {
val checkParent =
if(parent is TypecastExpression)
parent.parent
else
parent
if (checkParent !is Assignment && checkParent !is VarDecl) {
return "can't use subroutine call that returns multiple return values here"
}
}
}
}

View File

@ -1,18 +1,18 @@
package prog8.functions
package prog8.compiler.functions
import prog8.ast.IMemSizer
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.StructDecl
import prog8.ast.statements.VarDecl
import prog8.compiler.CompilerException
import kotlin.math.*
class FParam(val name: String, val possibleDatatypes: Set<DataType>)
class FParam(val name: String, val possibleDatatypes: Array<DataType>)
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteralValue
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer) -> NumericLiteralValue
class ReturnConvention(val dt: DataType, val reg: RegisterOrPair?, val floatFac1: Boolean)
@ -87,88 +87,63 @@ class FSignature(val name: String,
}
}
@Suppress("UNUSED_ANONYMOUS_PARAMETER")
private val functionSignatures: List<FSignature> = listOf(
// this set of function have no return value and operate in-place:
FSignature("rol" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
FSignature("ror" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
FSignature("rol2" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
FSignature("ror2" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
FSignature("rol" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
FSignature("ror" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
FSignature("rol2" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
FSignature("ror2" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
FSignature("sort" , false, listOf(FParam("array", ArrayDatatypes)), null),
FSignature("reverse" , false, listOf(FParam("array", ArrayDatatypes)), null),
FSignature("cmp" , false, listOf(FParam("value1", IntegerDatatypes), FParam("value2", NumericDatatypes)), null),
// these few have a return value depending on the argument(s):
FSignature("max" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
FSignature("min" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
FSignature("sum" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
FSignature("max" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
FSignature("min" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
FSignature("sum" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
FSignature("abs" , true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
FSignature("len" , true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
FSignature("sizeof" , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinSizeof),
FSignature("sizeof" , true, listOf(FParam("object", DataType.values())), DataType.UBYTE, ::builtinSizeof),
// normal functions follow:
FSignature("sgn" , true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
FSignature("sin" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
FSignature("sin8" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
FSignature("sin8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
FSignature("sin16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
FSignature("sin16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
FSignature("cos" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
FSignature("cos8" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
FSignature("cos8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
FSignature("cos16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
FSignature("cos16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
FSignature("tan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
FSignature("atan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
FSignature("ln" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
FSignature("log2" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
FSignature("sqrt16" , true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
FSignature("sqrt" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
FSignature("rad" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
FSignature("deg" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
FSignature("round" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
FSignature("floor" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
FSignature("ceil" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
FSignature("any" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
FSignature("all" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
FSignature("lsb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
FSignature("msb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}},
FSignature("mkword" , true, listOf(FParam("msb", setOf(DataType.UBYTE)), FParam("lsb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
FSignature("sin" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sin) },
FSignature("sin8" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
FSignature("sin8u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
FSignature("sin16" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
FSignature("sin16u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
FSignature("cos" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::cos) },
FSignature("cos8" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
FSignature("cos8u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
FSignature("cos16" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
FSignature("cos16u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
FSignature("tan" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::tan) },
FSignature("atan" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::atan) },
FSignature("ln" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::log) },
FSignature("log2" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, ::log2) },
FSignature("sqrt16" , true, listOf(FParam("value", arrayOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
FSignature("sqrt" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sqrt) },
FSignature("rad" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toRadians) },
FSignature("deg" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toDegrees) },
FSignature("round" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
FSignature("floor" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
FSignature("ceil" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
FSignature("any" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinAny) },
FSignature("all" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinAll) },
FSignature("lsb" , true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 } },
FSignature("msb" , true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255} },
FSignature("mkword" , true, listOf(FParam("msb", arrayOf(DataType.UBYTE)), FParam("lsb", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
FSignature("peek" , true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UBYTE),
FSignature("peekw" , true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
FSignature("poke" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
FSignature("pokew" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UWORD))), null),
FSignature("rnd" , false, emptyList(), DataType.UBYTE),
FSignature("rndw" , false, emptyList(), DataType.UWORD),
FSignature("rndf" , false, emptyList(), DataType.FLOAT),
FSignature("exit" , false, listOf(FParam("returnvalue", setOf(DataType.UBYTE))), null),
FSignature("rsave" , false, emptyList(), null),
FSignature("rrestore" , false, emptyList(), null),
FSignature("set_carry" , false, emptyList(), null),
FSignature("clear_carry" , false, emptyList(), null),
FSignature("set_irqd" , false, emptyList(), null),
FSignature("clear_irqd" , false, emptyList(), null),
FSignature("read_flags" , false, emptyList(), DataType.UBYTE),
FSignature("memory" , true, listOf(FParam("name", arrayOf(DataType.STR)), FParam("size", arrayOf(DataType.UWORD))), DataType.UWORD),
FSignature("swap" , false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null),
FSignature("memcopy" , false, listOf(
FParam("from", IterableDatatypes + DataType.UWORD),
FParam("to", IterableDatatypes + DataType.UWORD),
FParam("numbytes", setOf(DataType.UBYTE, DataType.UWORD))), null),
FSignature("memset" , false, listOf(
FParam("address", IterableDatatypes + DataType.UWORD),
FParam("numbytes", setOf(DataType.UWORD)),
FParam("bytevalue", ByteDatatypes)), null),
FSignature("memsetw" , false, listOf(
FParam("address", IterableDatatypes + DataType.UWORD),
FParam("numwords", setOf(DataType.UWORD)),
FParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
FSignature("strlen" , true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen),
FSignature("substr" , false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("start", setOf(DataType.UBYTE)),
FParam("length", setOf(DataType.UBYTE))), null),
FSignature("leftstr" , false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("length", setOf(DataType.UBYTE))), null),
FSignature("rightstr" , false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("length", setOf(DataType.UBYTE))), null),
FSignature("strcmp" , false, listOf(FParam("s1", IterableDatatypes + DataType.UWORD), FParam("s2", IterableDatatypes + DataType.UWORD)), DataType.BYTE, null)
FSignature("callfar" , false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), null),
FSignature("callrom" , false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), null),
)
val BuiltinFunctions = functionSignatures.associateBy { it.name }
@ -178,7 +153,7 @@ fun builtinMax(array: List<Number>): Number = array.maxByOrNull { it.toDouble()
fun builtinMin(array: List<Number>): Number = array.minByOrNull { it.toDouble() }!!
fun builtinSum(array: List<Number>): Number = array.sumByDouble { it.toDouble() }
fun builtinSum(array: List<Number>): Number = array.sumOf { it.toDouble() }
fun builtinAny(array: List<Number>): Number = if(array.any { it.toDouble()!=0.0 }) 1 else 0
@ -189,7 +164,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
fun datatypeFromIterableArg(arglist: Expression): DataType {
if(arglist is ArrayLiteralValue) {
val dt = arglist.value.map {it.inferType(program).typeOrElse(DataType.STRUCT)}.toSet()
val dt = arglist.value.map {it.inferType(program).getOr(DataType.UNDEFINED)}.toSet()
if(dt.any { it !in NumericDatatypes }) {
throw FatalAstException("fuction $function only accepts array of numeric values")
}
@ -203,9 +178,9 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
val idt = arglist.inferType(program)
if(!idt.isKnown)
throw FatalAstException("couldn't determine type of iterable $arglist")
return when(val dt = idt.typeOrElse(DataType.STRUCT)) {
return when(val dt = idt.getOr(DataType.UNDEFINED)) {
DataType.STR, in NumericDatatypes -> dt
in ArrayDatatypes -> ArrayElementTypes.getValue(dt)
in ArrayDatatypes -> ArrayToElementTypes.getValue(dt)
else -> throw FatalAstException("function '$function' requires one argument which is an iterable")
}
}
@ -220,7 +195,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
return when (function) {
"abs" -> {
val dt = args.single().inferType(program)
return if(dt.typeOrElse(DataType.STRUCT) in NumericDatatypes)
return if(dt.isNumeric)
dt
else
InferredTypes.InferredType.unknown()
@ -229,7 +204,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
when(val dt = datatypeFromIterableArg(args.single())) {
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
in NumericDatatypes -> InferredTypes.knownFor(dt)
in ArrayDatatypes -> InferredTypes.knownFor(ArrayElementTypes.getValue(dt))
in ArrayDatatypes -> InferredTypes.knownFor(ArrayToElementTypes.getValue(dt))
else -> InferredTypes.unknown()
}
}
@ -298,7 +273,8 @@ private fun collectionArg(args: List<Expression>, position: Position, program: P
return NumericLiteralValue.optimalNumeric(function(constElements.mapNotNull { it }), args[0].position)
}
private fun builtinAbs(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinAbs(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
// 1 arg, type = float or int, result type= isSameAs as argument type
if(args.size!=1)
throw SyntaxError("abs requires one numeric argument", position)
@ -311,7 +287,7 @@ private fun builtinAbs(args: List<Expression>, position: Position, program: Prog
}
}
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
// 1 arg, type = anything, result type = ubyte
if(args.size!=1)
throw SyntaxError("sizeof requires one argument", position)
@ -320,56 +296,30 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
val dt = args[0].inferType(program)
if(dt.isKnown) {
val target = (args[0] as IdentifierReference).targetStatement(program.namespace)
val target = (args[0] as IdentifierReference).targetStatement(program)
?: throw CannotEvaluateException("sizeof", "no target")
fun structSize(target: StructDecl) =
NumericLiteralValue(DataType.UBYTE, target.statements.map { (it as VarDecl).datatype.memorySize() }.sum(), position)
return when {
dt.typeOrElse(DataType.STRUCT) in ArrayDatatypes -> {
dt.isArray -> {
val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size")
val elementDt = ArrayElementTypes.getValue(dt.typeOrElse(DataType.STRUCT))
numericLiteral(elementDt.memorySize() * length, position)
val elementDt = ArrayToElementTypes.getValue(dt.getOr(DataType.UNDEFINED))
numericLiteral(memsizer.memorySize(elementDt) * length, position)
}
dt.istype(DataType.STRUCT) -> {
when (target) {
is VarDecl -> structSize(target.struct!!)
is StructDecl -> structSize(target)
else -> throw CompilerException("weird struct type $target")
}
}
dt.istype(DataType.STR) -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
else -> NumericLiteralValue(DataType.UBYTE, dt.typeOrElse(DataType.STRUCT).memorySize(), position)
dt istype DataType.STR -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
else -> NumericLiteralValue(DataType.UBYTE, memsizer.memorySize(dt.getOr(DataType.UNDEFINED)), position)
}
} else {
throw SyntaxError("sizeof invalid argument type", position)
}
}
private fun builtinStrlen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("strlen requires one argument", position)
val argument=args[0]
if(argument is StringLiteralValue)
return NumericLiteralValue.optimalInteger(argument.value.length, argument.position)
val vardecl = (argument as IdentifierReference).targetVarDecl(program.namespace)
if(vardecl!=null) {
if(vardecl.datatype!=DataType.STR && vardecl.datatype!=DataType.UWORD)
throw SyntaxError("strlen must have string argument", position)
if(vardecl.autogeneratedDontRemove && vardecl.value!=null) {
return NumericLiteralValue.optimalInteger((vardecl.value as StringLiteralValue).value.length, argument.position)
}
}
throw NotConstArgumentException()
}
private fun builtinLen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
// note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE.
@Suppress("UNUSED_PARAMETER")
private fun builtinLen(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
// note: in some cases the length is > 255, and then we have to return a UWORD type instead of a UBYTE.
if(args.size!=1)
throw SyntaxError("len requires one argument", position)
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program.namespace)
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program)
var arraySize = directMemVar?.arraysize?.constIndex()
if(arraySize != null)
return NumericLiteralValue.optimalInteger(arraySize, position)
@ -377,7 +327,7 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
return NumericLiteralValue.optimalInteger((args[0] as ArrayLiteralValue).value.size, position)
if(args[0] !is IdentifierReference)
throw SyntaxError("len argument should be an identifier", position)
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)
val target = (args[0] as IdentifierReference).targetVarDecl(program)
?: throw CannotEvaluateException("len", "no target vardecl")
return when(target.datatype) {
@ -388,17 +338,17 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
}
DataType.STR -> {
val refLv = target.value as StringLiteralValue
val refLv = target.value as? StringLiteralValue ?: throw CannotEvaluateException("len", "stringsize unknown")
NumericLiteralValue.optimalInteger(refLv.value.length, args[0].position)
}
DataType.STRUCT -> throw SyntaxError("cannot use len on struct, did you mean sizeof?", args[0].position)
in NumericDatatypes -> throw SyntaxError("cannot use len on numeric value, did you mean sizeof?", args[0].position)
else -> throw CompilerException("weird datatype")
}
}
private fun builtinMkword(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinMkword(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 2)
throw SyntaxError("mkword requires msb and lsb arguments", position)
val constMsb = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -407,7 +357,8 @@ private fun builtinMkword(args: List<Expression>, position: Position, program: P
return NumericLiteralValue(DataType.UWORD, result, position)
}
private fun builtinSin8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinSin8(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("sin8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -415,7 +366,8 @@ private fun builtinSin8(args: List<Expression>, position: Position, program: Pro
return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toInt().toShort(), position)
}
private fun builtinSin8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinSin8u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("sin8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -423,7 +375,8 @@ private fun builtinSin8u(args: List<Expression>, position: Position, program: Pr
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toInt().toShort(), position)
}
private fun builtinCos8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinCos8(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("cos8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -431,7 +384,8 @@ private fun builtinCos8(args: List<Expression>, position: Position, program: Pro
return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toInt().toShort(), position)
}
private fun builtinCos8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinCos8u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("cos8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -439,7 +393,8 @@ private fun builtinCos8u(args: List<Expression>, position: Position, program: Pr
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toInt().toShort(), position)
}
private fun builtinSin16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinSin16(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("sin16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -447,7 +402,8 @@ private fun builtinSin16(args: List<Expression>, position: Position, program: Pr
return NumericLiteralValue(DataType.WORD, (32767.0 * sin(rad)).toInt(), position)
}
private fun builtinSin16u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinSin16u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("sin16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -455,7 +411,8 @@ private fun builtinSin16u(args: List<Expression>, position: Position, program: P
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * sin(rad)).toInt(), position)
}
private fun builtinCos16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinCos16(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("cos16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -463,7 +420,8 @@ private fun builtinCos16(args: List<Expression>, position: Position, program: Pr
return NumericLiteralValue(DataType.WORD, (32767.0 * cos(rad)).toInt(), position)
}
private fun builtinCos16u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinCos16u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("cos16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -471,7 +429,8 @@ private fun builtinCos16u(args: List<Expression>, position: Position, program: P
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * cos(rad)).toInt(), position)
}
private fun builtinSgn(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinSgn(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("sgn requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()

View File

@ -1,47 +0,0 @@
package prog8.compiler.target
import prog8.ast.Program
import prog8.ast.base.ErrorReporter
import prog8.compiler.CompilationOptions
import prog8.compiler.Zeropage
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.c64.codegen.AsmGen
import prog8.compiler.target.cx16.CX16MachineDefinition
import java.nio.file.Path
internal interface CompilationTarget {
val name: String
val machine: IMachineDefinition
fun encodeString(str: String, altEncoding: Boolean): List<Short>
fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path): IAssemblyGenerator
companion object {
lateinit var instance: CompilationTarget
}
}
internal object C64Target: CompilationTarget {
override val name = "c64"
override val machine = C64MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean) =
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path) =
AsmGen(program, errors, zp, options, path)
}
internal object Cx16Target: CompilationTarget {
override val name = "cx16"
override val machine = CX16MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean) =
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path) =
AsmGen(program, errors, zp, options, path)
}

View File

@ -3,7 +3,7 @@ package prog8.compiler.target
import prog8.compiler.CompilationOptions
internal interface IAssemblyGenerator {
fun compileToAssembly(optimize: Boolean): IAssemblyProgram
fun compileToAssembly(): IAssemblyProgram
}
internal const val generatedLabelPrefix = "_prog8_label_"
@ -11,6 +11,7 @@ internal const val subroutineFloatEvalResultVar1 = "_prog8_float_eval_result1"
internal const val subroutineFloatEvalResultVar2 = "_prog8_float_eval_result2"
internal interface IAssemblyProgram {
val valid: Boolean
val name: String
fun assemble(options: CompilationOptions)
fun assemble(options: CompilationOptions): Int
}

View File

@ -0,0 +1,98 @@
package prog8.compiler.target
import com.github.michaelbull.result.fold
import prog8.ast.IMemSizer
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.AssignTarget
import prog8.compiler.CompilationOptions
import prog8.compiler.IErrorReporter
import prog8.compiler.IStringEncoding
import prog8.compiler.Zeropage
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.cbm.Petscii
import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.compiler.target.cx16.CX16MachineDefinition
import java.io.CharConversionException
import java.nio.file.Path
interface ICompilationTarget: IStringEncoding, IMemSizer {
val name: String
val machine: IMachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short>
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
}
internal object C64Target: ICompilationTarget {
override val name = "c64"
override val machine = C64MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return coded.fold(
failure = { throw it },
success = { it }
)
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
try {
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
} catch (x: CharConversionException) {
throw CharConversionException("can't decode string: ${x.message}")
}
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> -9999999
}
}
}
internal object Cx16Target: ICompilationTarget {
override val name = "cx16"
override val machine = CX16MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
val coded= if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return coded.fold(
failure = { throw it },
success = { it }
)
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
try {
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
} catch (x: CharConversionException) {
throw CharConversionException("can't decode string: ${x.message}")
}
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> -9999999
}
}
}
internal fun asmGeneratorFor(
compTarget: ICompilationTarget,
program: Program,
errors: IErrorReporter,
zp: Zeropage,
options: CompilationOptions,
outputDir: Path
): IAssemblyGenerator
{
// at the moment we only have one code generation backend (for 6502 and 65c02)
return AsmGen(program, errors, zp, options, compTarget, outputDir)
}

View File

@ -1,22 +1,21 @@
package prog8.compiler.target
import prog8.ast.Program
import prog8.compiler.CompilationOptions
import prog8.compiler.Zeropage
import prog8.parser.ModuleImporter
import java.nio.file.Path
internal interface IMachineFloat {
interface IMachineFloat {
fun toDouble(): Double
fun makeFloatFillAsm(): String
}
internal enum class CpuType {
enum class CpuType {
CPU6502,
CPU65c02
}
internal interface IMachineDefinition {
interface IMachineDefinition {
val FLOAT_MAX_NEGATIVE: Double
val FLOAT_MAX_POSITIVE: Double
val FLOAT_MEM_SIZE: Int
@ -32,8 +31,8 @@ internal interface IMachineDefinition {
fun initializeZeropage(compilerOptions: CompilationOptions)
fun getFloat(num: Number): IMachineFloat
fun getFloatRomConst(number: Double): String?
fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program)
fun launchEmulator(programName: String)
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path)
fun isRegularRAMaddress(address: Int): Boolean
}

View File

@ -1,13 +1,12 @@
package prog8.compiler.target.c64
import prog8.ast.Program
import prog8.compiler.*
import prog8.compiler.target.CpuType
import prog8.compiler.target.IMachineDefinition
import prog8.compiler.target.IMachineFloat
import prog8.parser.ModuleImporter
import prog8.compiler.target.cbm.viceMonListPostfix
import java.io.IOException
import java.math.RoundingMode
import java.nio.file.Path
import kotlin.math.absoluteValue
import kotlin.math.pow
@ -24,7 +23,6 @@ internal object C64MachineDefinition: IMachineDefinition {
override val RAW_LOAD_ADDRESS = 0xc000
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
// and some heavily used string constants derived from the two values above
override val ESTACK_LO = 0xce00 // $ce00-$ceff inclusive
override val ESTACK_HI = 0xcf00 // $ce00-$ceff inclusive
@ -32,51 +30,23 @@ internal object C64MachineDefinition: IMachineDefinition {
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
override fun getFloatRomConst(number: Double): String? {
// try to match the ROM float constants to save memory
val mflpt5 = Mflpt5.fromNumber(number)
val floatbytes = shortArrayOf(mflpt5.b0, mflpt5.b1, mflpt5.b2, mflpt5.b3, mflpt5.b4)
when {
floatbytes.contentEquals(shortArrayOf(0x00, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_ZERO_const" // not a ROM const
floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_ONE_const" // not a ROM const
floatbytes.contentEquals(shortArrayOf(0x82, 0x49, 0x0f, 0xda, 0xa1)) -> return "floats.FL_PIVAL"
floatbytes.contentEquals(shortArrayOf(0x90, 0x80, 0x00, 0x00, 0x00)) -> return "floats.FL_N32768"
floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FONE"
floatbytes.contentEquals(shortArrayOf(0x80, 0x35, 0x04, 0xf3, 0x34)) -> return "floats.FL_SQRHLF"
floatbytes.contentEquals(shortArrayOf(0x81, 0x35, 0x04, 0xf3, 0x34)) -> return "floats.FL_SQRTWO"
floatbytes.contentEquals(shortArrayOf(0x80, 0x80, 0x00, 0x00, 0x00)) -> return "floats.FL_NEGHLF"
floatbytes.contentEquals(shortArrayOf(0x80, 0x31, 0x72, 0x17, 0xf8)) -> return "floats.FL_LOG2"
floatbytes.contentEquals(shortArrayOf(0x84, 0x20, 0x00, 0x00, 0x00)) -> return "floats.FL_TENC"
floatbytes.contentEquals(shortArrayOf(0x9e, 0x6e, 0x6b, 0x28, 0x00)) -> return "floats.FL_NZMIL"
floatbytes.contentEquals(shortArrayOf(0x80, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FHALF"
floatbytes.contentEquals(shortArrayOf(0x81, 0x38, 0xaa, 0x3b, 0x29)) -> return "floats.FL_LOGEB2"
floatbytes.contentEquals(shortArrayOf(0x81, 0x49, 0x0f, 0xda, 0xa2)) -> return "floats.FL_PIHALF"
floatbytes.contentEquals(shortArrayOf(0x83, 0x49, 0x0f, 0xda, 0xa2)) -> return "floats.FL_TWOPI"
floatbytes.contentEquals(shortArrayOf(0x7f, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FR4"
else -> {
// attempt to correct for a few rounding issues
when (number.toBigDecimal().setScale(10, RoundingMode.HALF_DOWN).toDouble()) {
3.1415926536 -> return "floats.FL_PIVAL"
1.4142135624 -> return "floats.FL_SQRTWO"
0.7071067812 -> return "floats.FL_SQRHLF"
0.6931471806 -> return "floats.FL_LOG2"
else -> {}
}
}
override fun importLibs(compilerOptions: CompilationOptions,compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
if(selectedEmulator!=1) {
System.err.println("The c64 target only supports the main emulator (Vice).")
return
}
return null
}
override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) {
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
importer.importLibraryModule(program, "syslib")
}
override fun launchEmulator(programName: String) {
for(emulator in listOf("x64sc", "x64")) {
println("\nStarting C-64 emulator $emulator...")
val cmdline = listOf(emulator, "-silent", "-moncommands", "$programName.vice-mon-list",
"-autostartprgmode", "1", "-autostart-warp", "-autostart", programName + ".prg")
val cmdline = listOf(emulator, "-silent", "-moncommands", "${programNameWithPath}.$viceMonListPostfix",
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process
try {
@ -116,7 +86,7 @@ internal object C64MachineDefinition: IMachineDefinition {
init {
if (options.floats && options.zeropage !in setOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
if (options.floats && options.zeropage !in arrayOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
if (options.zeropage == ZeropageType.FULL) {
@ -139,13 +109,14 @@ internal object C64MachineDefinition: IMachineDefinition {
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
// 0x90-0xfa is 'kernel work storage area'
// 0x90-0xfa is 'kernal work storage area'
))
}
if (options.zeropage == ZeropageType.FLOATSAFE) {
// remove the zero page locations used for floating point operations from the free list
// remove the zeropage locations used for floating point operations from the free list
free.removeAll(listOf(
0x22, 0x23, 0x24, 0x25,
0x10, 0x11, 0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,

File diff suppressed because it is too large Load Diff

View File

@ -1,270 +0,0 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.ast.statements.SubroutineParameter
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.codegen.assignment.*
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translateFunctionCallStatement(stmt: IFunctionCall) {
val sub = stmt.target.targetSubroutine(program.namespace)!!
val preserveStatusRegisterAfterCall = sub.asmReturnvaluesRegisters.any {it.statusflag!=null}
translateFunctionCall(stmt, preserveStatusRegisterAfterCall)
// functioncalls no longer return results on the stack, so simply ignore the results in the registers
if(preserveStatusRegisterAfterCall)
asmgen.out(" plp\t; restore status flags from call")
}
internal fun translateFunctionCall(stmt: IFunctionCall, preserveStatusRegisterAfterCall: Boolean) {
// output the code to setup the parameters and perform the actual call
// does NOT output the code to deal with the result values!
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
val saveX = CpuRegister.X in sub.asmClobbers || sub.regXasResult() || sub.regXasParam()
if(saveX)
asmgen.saveRegister(CpuRegister.X, preserveStatusRegisterAfterCall, (stmt as Node).definingSubroutine()!!)
val subName = asmgen.asmSymbolName(stmt.target)
if(stmt.args.isNotEmpty()) {
if(sub.asmParameterRegisters.isEmpty()) {
// via variables
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
argumentViaVariable(sub, arg.first, arg.second)
}
} else {
// via registers
if(sub.parameters.size==1) {
// just a single parameter, no risk of clobbering registers
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), stmt.args[0])
} else {
// multiple register arguments, risk of register clobbering.
// evaluate arguments onto the stack, and load the registers from the evaluated values on the stack.
when {
stmt.args.all {it is AddressOf ||
it is NumericLiteralValue ||
it is StringLiteralValue ||
it is ArrayLiteralValue ||
it is IdentifierReference} -> {
// no risk of clobbering for these simple argument types. Optimize the register loading.
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
argumentViaRegister(sub, arg.first, arg.second)
}
}
else -> {
// Risk of clobbering due to complex expression args. Work via the stack.
registerArgsViaStackEvaluation(stmt, sub)
}
}
}
}
}
asmgen.out(" jsr $subName")
if(preserveStatusRegisterAfterCall) {
asmgen.out(" php\t; save status flags from call")
// note: the containing statement (such as the FunctionCallStatement or the Assignment or the Expression)
// must take care of popping this value again at the end!
}
if(saveX)
asmgen.restoreRegister(CpuRegister.X, preserveStatusRegisterAfterCall)
}
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
// this is called when one or more of the arguments are 'complex' and
// cannot be assigned to a register easily or risk clobbering other registers.
if(sub.parameters.isEmpty())
return
// 1. load all arguments reversed onto the stack: first arg goes last (is on top).
for (arg in stmt.args.reversed())
asmgen.translateExpression(arg)
var argForCarry: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
var argForXregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
var argForAregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
asmgen.out(" inx") // align estack pointer
for(argi in stmt.args.zip(sub.asmParameterRegisters).withIndex()) {
when {
argi.value.second.statusflag == Statusflag.Pc -> {
require(argForCarry == null)
argForCarry = argi
}
argi.value.second.statusflag != null -> throw AssemblyError("can only use Carry as status flag parameter")
argi.value.second.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) -> {
require(argForXregister==null)
argForXregister = argi
}
argi.value.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.AY) -> {
require(argForAregister == null)
argForAregister = argi
}
argi.value.second.registerOrPair == RegisterOrPair.Y -> {
asmgen.out(" ldy P8ESTACK_LO+${argi.index},x")
}
else -> throw AssemblyError("weird argument")
}
}
if(argForCarry!=null) {
asmgen.out("""
lda P8ESTACK_LO+${argForCarry.index},x
beq +
sec
bcs ++
+ clc
+ php""") // push the status flags
}
if(argForAregister!=null) {
when(argForAregister.value.second.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" lda P8ESTACK_LO+${argForAregister.index},x")
RegisterOrPair.AY -> asmgen.out(" lda P8ESTACK_LO+${argForAregister.index},x | ldy P8ESTACK_HI+${argForAregister.index},x")
else -> throw AssemblyError("weird arg")
}
}
if(argForXregister!=null) {
if(argForAregister!=null)
asmgen.out(" pha")
when(argForXregister.value.second.registerOrPair) {
RegisterOrPair.X -> asmgen.out(" lda P8ESTACK_LO+${argForXregister.index},x | tax")
RegisterOrPair.AX -> asmgen.out(" ldy P8ESTACK_LO+${argForXregister.index},x | lda P8ESTACK_HI+${argForXregister.index},x | tax | tya")
RegisterOrPair.XY -> asmgen.out(" ldy P8ESTACK_HI+${argForXregister.index},x | lda P8ESTACK_LO+${argForXregister.index},x | tax")
else -> throw AssemblyError("weird arg")
}
if(argForAregister!=null)
asmgen.out(" pla")
} else {
repeat(sub.parameters.size - 1) { asmgen.out(" inx") } // unwind stack
}
if(argForCarry!=null)
asmgen.out(" plp") // set the carry flag back to correct value
}
private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
// pass parameter via a regular variable (not via registers)
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("unknown dt")
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
val varName = asmgen.asmVariableName(sub.scopedname+"."+parameter.value.name)
asmgen.assignExpressionToVariable(value, varName, parameter.value.type, sub)
}
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
// pass argument via a register parameter
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("unknown dt")
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
val paramRegister = sub.asmParameterRegisters[parameter.index]
val statusflag = paramRegister.statusflag
val register = paramRegister.registerOrPair
val requiredDt = parameter.value.type
if(requiredDt!=valueDt) {
if(valueDt largerThan requiredDt)
throw AssemblyError("can only convert byte values to word param types")
}
when {
statusflag!=null -> {
if(requiredDt!=valueDt)
throw AssemblyError("for statusflag, byte value is required")
if (statusflag == Statusflag.Pc) {
// this param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
when(value) {
is NumericLiteralValue -> {
val carrySet = value.number.toInt() != 0
asmgen.out(if(carrySet) " sec" else " clc")
}
is IdentifierReference -> {
val sourceName = asmgen.asmVariableName(value)
asmgen.out("""
pha
lda $sourceName
beq +
sec
bcs ++
+ clc
+ pla
""")
}
else -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.out("""
beq +
sec
bcs ++
+ clc
+""")
}
}
}
else throw AssemblyError("can only use Carry as status flag parameter")
}
else -> {
// via register or register pair
register!!
if(requiredDt largerThan valueDt) {
// we need to sign extend the source, do this via temporary word variable
val scratchVar = asmgen.asmVariableName("P8ZP_SCRATCH_W1")
asmgen.assignExpressionToVariable(value, scratchVar, DataType.UBYTE, sub)
asmgen.signExtendVariableLsb(scratchVar, valueDt)
asmgen.assignVariableToRegister(scratchVar, register)
}
else {
val target = AsmAssignTarget.fromRegisters(register, sub, program, asmgen)
val src = if(valueDt in PassByReferenceDatatypes) {
if(value is IdentifierReference) {
val addr = AddressOf(value, Position.DUMMY)
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
}
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
}
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY))
}
}
}
}
private fun isArgumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
if(argType isAssignableTo paramType)
return true
if(argType in ByteDatatypes && paramType in ByteDatatypes)
return true
if(argType in WordDatatypes && paramType in WordDatatypes)
return true
// we have a special rule for some types.
// strings are assignable to UWORD, for example, and vice versa
if(argType==DataType.STR && paramType==DataType.UWORD)
return true
if(argType==DataType.UWORD && paramType == DataType.STR)
return true
return false
}
}

View File

@ -1,20 +1,26 @@
package prog8.compiler.target.c64
package prog8.compiler.target.cbm
import prog8.compiler.CompilationOptions
import prog8.compiler.OutputType
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.IAssemblyProgram
import prog8.compiler.target.generatedLabelPrefix
import java.nio.file.Path
import kotlin.system.exitProcess
class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyProgram {
internal const val viceMonListPostfix = "vice-mon-list"
class AssemblyProgram(
override val valid: Boolean,
override val name: String,
outputDir: Path,
private val compTarget: String) : IAssemblyProgram {
private val assemblyFile = outputDir.resolve("$name.asm")
private val prgFile = outputDir.resolve("$name.prg")
private val binFile = outputDir.resolve("$name.bin")
private val viceMonListFile = outputDir.resolve("$name.vice-mon-list")
private val viceMonListFile = outputDir.resolve("$name.$viceMonListPostfix")
override fun assemble(options: CompilationOptions) {
override fun assemble(options: CompilationOptions): Int {
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
@ -23,12 +29,12 @@ class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyPro
val outFile = when (options.output) {
OutputType.PRG -> {
command.add("--cbm-prg")
println("\nCreating prg for target ${CompilationTarget.instance.name}.")
println("\nCreating prg for target $compTarget.")
prgFile
}
OutputType.RAW -> {
command.add("--nostart")
println("\nCreating raw binary for target ${CompilationTarget.instance.name}.")
println("\nCreating raw binary for target $compTarget.")
binFile
}
}
@ -36,13 +42,11 @@ class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyPro
val proc = ProcessBuilder(command).inheritIO().start()
val result = proc.waitFor()
if (result != 0) {
System.err.println("assembler failed with returncode $result")
exitProcess(result)
if (result == 0) {
removeGeneratedLabelsFromMonlist()
generateBreakpointList()
}
removeGeneratedLabelsFromMonlist()
generateBreakpointList()
return result
}
private fun removeGeneratedLabelsFromMonlist() {

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
package prog8.compiler.target.c64.codegen
package prog8.compiler.target.cpu6502.codegen
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
@ -74,12 +74,12 @@ private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// the when statement (on bytes) generates a sequence of:
// lda $ce01,x
// cmp #$20
// when statement (on bytes) generates a sequence of:
// lda $ce01,x
// cmp #$20
// beq check_prog8_s72choice_32
// lda $ce01,x
// cmp #$21
// lda $ce01,x
// cmp #$21
// beq check_prog8_s73choice_33
// the repeated lda can be removed
val mods = mutableListOf<Modification>()
@ -179,7 +179,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
}
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can be eliminated
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
// TODO this is not true if X is not a regular RAM memory address (but instead mapped I/O or ROM)
val mods = mutableListOf<Modification>()
for (pair in linesByFour) {
@ -196,10 +196,14 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
(first.startsWith("stx ") && second.startsWith("ldx "))
) {
val firstLoc = first.substring(4).trimStart()
val secondLoc = second.substring(4).trimStart()
if (firstLoc == secondLoc) {
mods.add(Modification(pair[1].index, true, null))
val third = pair[2].value.trimStart()
if(!third.startsWith("b")) {
// no branch instruction follows, we can potentiall remove the load instruction
val firstLoc = first.substring(4).trimStart()
val secondLoc = second.substring(4).trimStart()
if (firstLoc == secondLoc) {
mods.add(Modification(pair[1].index, true, null))
}
}
}
}

View File

@ -1,14 +1,15 @@
package prog8.compiler.target.c64.codegen
package prog8.compiler.target.cpu6502.codegen
import prog8.ast.Program
import prog8.ast.base.ArrayElementTypes
import prog8.ast.base.ArrayToElementTypes
import prog8.ast.base.DataType
import prog8.ast.base.RegisterOrPair
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.RangeExpr
import prog8.ast.statements.ForLoop
import prog8.ast.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.toHex
import prog8.compiler.astprocessing.toConstantIntegerRange
import kotlin.math.absoluteValue
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
@ -19,15 +20,15 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
throw AssemblyError("unknown dt")
when(stmt.iterable) {
is RangeExpr -> {
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange(asmgen.options.compTarget)
if(range==null) {
translateForOverNonconstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as RangeExpr)
translateForOverNonconstRange(stmt, iterableDt.getOr(DataType.UNDEFINED), stmt.iterable as RangeExpr)
} else {
translateForOverConstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), range)
translateForOverConstRange(stmt, iterableDt.getOr(DataType.UNDEFINED), range)
}
}
is IdentifierReference -> {
translateForOverIterableVar(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as IdentifierReference)
translateForOverIterableVar(stmt, iterableDt.getOr(DataType.UNDEFINED), stmt.iterable as IdentifierReference)
}
else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable")
}
@ -40,6 +41,13 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
asmgen.loopEndLabels.push(endLabel)
val stepsize=range.step.constValue(program)!!.number.toInt()
if(stepsize < -1) {
val limit = range.to.constValue(program)?.number?.toDouble()
if(limit==0.0)
throw AssemblyError("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping")
}
when(iterableDt) {
DataType.ARRAY_B, DataType.ARRAY_UB -> {
if (stepsize==1 || stepsize==-1) {
@ -49,17 +57,17 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
val incdec = if(stepsize==1) "inc" else "dec"
// loop over byte range via loopvar
val varname = asmgen.asmVariableName(stmt.loopVar)
asmgen.assignExpressionToVariable(range.from, varname, ArrayElementTypes.getValue(iterableDt), null)
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayElementTypes.getValue(iterableDt), null)
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null)
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null)
asmgen.out(loopLabel)
asmgen.translate(stmt.body)
asmgen.out("""
lda $varname
$modifiedLabel cmp #0 ; modified
beq $endLabel
$incdec $varname
jmp $loopLabel
$endLabel""")
$incdec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
} else {
@ -67,8 +75,8 @@ $endLabel""")
// loop over byte range via loopvar
val varname = asmgen.asmVariableName(stmt.loopVar)
asmgen.assignExpressionToVariable(range.from, varname, ArrayElementTypes.getValue(iterableDt), null)
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayElementTypes.getValue(iterableDt), null)
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null)
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null)
asmgen.out(loopLabel)
asmgen.translate(stmt.body)
if(stepsize>0) {
@ -117,16 +125,15 @@ $modifiedLabel2 cmp #0 ; modified
asmgen.out("""
+ inc $varname
bne $loopLabel
inc $varname+1
jmp $loopLabel
""")
inc $varname+1""")
asmgen.jmp(loopLabel)
} else {
asmgen.out("""
+ lda $varname
bne +
dec $varname+1
+ dec $varname
jmp $loopLabel""")
+ dec $varname""")
asmgen.jmp(loopLabel)
}
asmgen.out(endLabel)
}
@ -239,7 +246,7 @@ $endLabel""")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
val iterableName = asmgen.asmVariableName(ident)
val decl = ident.targetVarDecl(program.namespace)!!
val decl = ident.targetVarDecl(program)!!
when(iterableDt) {
DataType.STR -> {
asmgen.out("""
@ -282,7 +289,7 @@ $loopLabel sty $indexVar
bne $loopLabel
beq $endLabel""")
}
if(length>=16 && asmgen.zeropage.available() > 0) {
if(length>=16 && asmgen.zeropage.hasByteAvailable()) {
// allocate index var on ZP
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
@ -321,7 +328,7 @@ $loopLabel sty $indexVar
bne $loopLabel
beq $endLabel""")
}
if(length>=16 && asmgen.zeropage.available() > 0) {
if(length>=16 && asmgen.zeropage.hasByteAvailable()) {
// allocate index var on ZP
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
@ -369,7 +376,7 @@ $loopLabel""")
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
}
2 -> {
if(range.last==255) {
if(range.last==255 || range.last==254) {
asmgen.out("""
inc $varname
beq $endLabel
@ -377,34 +384,34 @@ $loopLabel""")
bne $loopLabel""")
} else {
asmgen.out("""
inc $varname
inc $varname
lda $varname
cmp #${range.last}
beq $endLabel
inc $varname
inc $varname
jmp $loopLabel""")
cmp #${range.last+2}
bne $loopLabel""")
}
}
-2 -> {
when (range.last) {
0 -> asmgen.out("""
lda $varname
beq $endLabel
dec $varname
dec $varname
jmp $loopLabel""")
0 -> {
asmgen.out("""
lda $varname
beq $endLabel
dec $varname
dec $varname""")
asmgen.jmp(loopLabel)
}
1 -> asmgen.out("""
dec $varname
beq $endLabel
dec $varname
bne $loopLabel""")
dec $varname
beq $endLabel
dec $varname
bne $loopLabel""")
else -> asmgen.out("""
lda $varname
cmp #${range.last}
beq $endLabel
dec $varname
dec $varname
jmp $loopLabel""")
dec $varname
dec $varname
lda $varname
cmp #${range.last-2}
bne $loopLabel""")
}
}
else -> {
@ -415,8 +422,8 @@ $loopLabel""")
beq $endLabel
clc
adc #${range.step}
sta $varname
jmp $loopLabel""")
sta $varname""")
asmgen.jmp(loopLabel)
}
}
asmgen.out(endLabel)
@ -452,9 +459,9 @@ $loopLabel""")
sta $varname
lda $varname+1
adc #>${range.step}
sta $varname+1
jmp $loopLabel
$endLabel""")
sta $varname+1""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
}
}
}
@ -480,11 +487,10 @@ $loopLabel""")
$endLabel""")
} else {
asmgen.out("""
lda $varname
cmp #${range.last}
beq $endLabel
inc $varname
jmp $loopLabel
lda $varname
cmp #${range.last+1}
bne $loopLabel
$endLabel""")
}
asmgen.loopEndLabels.pop()
@ -505,23 +511,22 @@ $loopLabel""")
asmgen.out("""
lda $varname
beq $endLabel
dec $varname
jmp $loopLabel
$endLabel""")
dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
}
1 -> {
asmgen.out("""
dec $varname
jmp $loopLabel
bne $loopLabel
$endLabel""")
}
else -> {
asmgen.out("""
lda $varname
cmp #${range.last}
beq $endLabel
dec $varname
jmp $loopLabel
lda $varname
cmp #${range.last-1}
bne $loopLabel
$endLabel""")
}
}
@ -546,13 +551,12 @@ $loopLabel""")
bne +
lda $varname+1
cmp #>${range.last}
bne +
beq $endLabel
+ inc $varname
bne $loopLabel
inc $varname+1
jmp $loopLabel
$endLabel""")
inc $varname+1""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
asmgen.loopEndLabels.pop()
}
@ -574,17 +578,16 @@ $loopLabel""")
bne +
lda $varname+1
cmp #>${range.last}
bne +
beq $endLabel
+ lda $varname
bne +
dec $varname+1
+ dec $varname
jmp $loopLabel
$endLabel""")
+ dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
asmgen.loopEndLabels.pop()
}
private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) =
asmgen.assignExpressionToVariable(range.from, asmgen.asmVariableName(stmt.loopVar), stmt.loopVarDt(program).typeOrElse(DataType.STRUCT), stmt.definingSubroutine())
asmgen.assignExpressionToVariable(range.from, asmgen.asmVariableName(stmt.loopVar), stmt.loopVarDt(program).getOr(DataType.UNDEFINED), stmt.definingSubroutine)
}

View File

@ -0,0 +1,361 @@
package prog8.compiler.target.cpu6502.codegen
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.InlineAssembly
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.ast.statements.SubroutineParameter
import prog8.compiler.AssemblyError
import prog8.compiler.target.CpuType
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignTarget
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
import prog8.compiler.target.cpu6502.codegen.assignment.TargetStorageKind
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translateFunctionCallStatement(stmt: IFunctionCall) {
saveXbeforeCall(stmt)
translateFunctionCall(stmt)
restoreXafterCall(stmt)
// just ignore any result values from the function call.
}
internal fun saveXbeforeCall(stmt: IFunctionCall) {
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
if(sub.shouldSaveX()) {
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
if(regSaveOnStack)
asmgen.saveRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnEntry)
else
asmgen.saveRegisterLocal(CpuRegister.X, (stmt as Node).definingSubroutine!!)
}
}
internal fun restoreXafterCall(stmt: IFunctionCall) {
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
if(sub.shouldSaveX()) {
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
if(regSaveOnStack)
asmgen.restoreRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnReturn)
else
asmgen.restoreRegisterLocal(CpuRegister.X)
}
}
internal fun translateFunctionCall(stmt: IFunctionCall) {
// Output only the code to set up the parameters and perform the actual call
// NOTE: does NOT output the code to deal with the result values!
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
val subName = asmgen.asmSymbolName(stmt.target)
if(stmt.args.isNotEmpty()) {
if(sub.asmParameterRegisters.isEmpty()) {
// via variables
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
argumentViaVariable(sub, arg.first, arg.second)
}
} else {
// via registers
if(sub.parameters.size==1) {
// just a single parameter, no risk of clobbering registers
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), stmt.args[0])
} else {
fun isNoClobberRisk(expr: Expression): Boolean {
if(expr is AddressOf ||
expr is NumericLiteralValue ||
expr is StringLiteralValue ||
expr is ArrayLiteralValue ||
expr is IdentifierReference)
return true
if(expr is FunctionCall) {
if(expr.target.nameInSource==listOf("lsb") || expr.target.nameInSource==listOf("msb"))
return isNoClobberRisk(expr.args[0])
if(expr.target.nameInSource==listOf("mkword"))
return isNoClobberRisk(expr.args[0]) && isNoClobberRisk(expr.args[1])
}
return false
}
when {
stmt.args.all {isNoClobberRisk(it)} -> {
// There's no risk of clobbering for these simple argument types. Optimize the register loading directly from these values.
// register assignment order: 1) cx16 virtual word registers, 2) actual CPU registers, 3) CPU Carry status flag.
val argsInfo = sub.parameters.withIndex().zip(stmt.args).zip(sub.asmParameterRegisters)
val (cx16virtualRegs, args2) = argsInfo.partition { it.second.registerOrPair in Cx16VirtualRegisters }
val (cpuRegs, statusRegs) = args2.partition { it.second.registerOrPair!=null }
for(arg in cx16virtualRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second)
for(arg in cpuRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second)
for(arg in statusRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second)
}
else -> {
// Risk of clobbering due to complex expression args. Evaluate first, then assign registers.
registerArgsViaStackEvaluation(stmt, sub)
}
}
}
}
}
if(!sub.inline || !asmgen.options.optimize) {
asmgen.out(" jsr $subName")
} else {
// inline the subroutine.
// we do this by copying the subroutine's statements at the call site.
// NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine
// (this condition has been enforced by an ast check earlier)
// note: for now, this is only reliably supported for asmsubs.
if(!sub.isAsmSubroutine)
throw AssemblyError("can only reliably inline asmsub routines at this time")
asmgen.out(" \t; inlined routine follows: ${sub.name}")
val assembly = sub.statements.single() as InlineAssembly
asmgen.translate(assembly)
asmgen.out(" \t; inlined routine end: ${sub.name}")
}
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
}
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
// this is called when one or more of the arguments are 'complex' and
// cannot be assigned to a register easily or risk clobbering other registers.
if(sub.parameters.isEmpty())
return
// 1. load all arguments reversed onto the stack: first arg goes last (is on top).
for (arg in stmt.args.reversed())
asmgen.translateExpression(arg)
var argForCarry: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
var argForXregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
var argForAregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
asmgen.out(" inx") // align estack pointer
for(argi in stmt.args.zip(sub.asmParameterRegisters).withIndex()) {
val plusIdxStr = if(argi.index==0) "" else "+${argi.index}"
when {
argi.value.second.statusflag == Statusflag.Pc -> {
require(argForCarry == null)
argForCarry = argi
}
argi.value.second.statusflag != null -> throw AssemblyError("can only use Carry as status flag parameter")
argi.value.second.registerOrPair in arrayOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) -> {
require(argForXregister==null)
argForXregister = argi
}
argi.value.second.registerOrPair in arrayOf(RegisterOrPair.A, RegisterOrPair.AY) -> {
require(argForAregister == null)
argForAregister = argi
}
argi.value.second.registerOrPair == RegisterOrPair.Y -> {
asmgen.out(" ldy P8ESTACK_LO$plusIdxStr,x")
}
argi.value.second.registerOrPair in Cx16VirtualRegisters -> {
// immediately output code to load the virtual register, to avoid clobbering the A register later
when (sub.parameters[argi.index].type) {
in ByteDatatypes -> {
// only load the lsb of the virtual register
asmgen.out(
"""
lda P8ESTACK_LO$plusIdxStr,x
sta cx16.${argi.value.second.registerOrPair.toString().lowercase()}
""")
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(
" stz cx16.${
argi.value.second.registerOrPair.toString().lowercase()
}+1")
else
asmgen.out(
" lda #0 | sta cx16.${
argi.value.second.registerOrPair.toString().lowercase()
}+1")
}
in WordDatatypes, in IterableDatatypes ->
asmgen.out(
"""
lda P8ESTACK_LO$plusIdxStr,x
sta cx16.${argi.value.second.registerOrPair.toString().lowercase()}
lda P8ESTACK_HI$plusIdxStr,x
sta cx16.${
argi.value.second.registerOrPair.toString().lowercase()
}+1
""")
else -> throw AssemblyError("weird dt")
}
}
else -> throw AssemblyError("weird argument")
}
}
if(argForCarry!=null) {
val plusIdxStr = if(argForCarry.index==0) "" else "+${argForCarry.index}"
asmgen.out("""
lda P8ESTACK_LO$plusIdxStr,x
beq +
sec
bcs ++
+ clc
+ php""") // push the status flags
}
if(argForAregister!=null) {
val plusIdxStr = if(argForAregister.index==0) "" else "+${argForAregister.index}"
when(argForAregister.value.second.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" lda P8ESTACK_LO$plusIdxStr,x")
RegisterOrPair.AY -> asmgen.out(" lda P8ESTACK_LO$plusIdxStr,x | ldy P8ESTACK_HI$plusIdxStr,x")
else -> throw AssemblyError("weird arg")
}
}
if(argForXregister!=null) {
val plusIdxStr = if(argForXregister.index==0) "" else "+${argForXregister.index}"
if(argForAregister!=null)
asmgen.out(" pha")
when(argForXregister.value.second.registerOrPair) {
RegisterOrPair.X -> asmgen.out(" lda P8ESTACK_LO$plusIdxStr,x | tax")
RegisterOrPair.AX -> asmgen.out(" ldy P8ESTACK_LO$plusIdxStr,x | lda P8ESTACK_HI$plusIdxStr,x | tax | tya")
RegisterOrPair.XY -> asmgen.out(" ldy P8ESTACK_HI$plusIdxStr,x | lda P8ESTACK_LO$plusIdxStr,x | tax")
else -> throw AssemblyError("weird arg")
}
if(argForAregister!=null)
asmgen.out(" pla")
} else {
repeat(sub.parameters.size - 1) { asmgen.out(" inx") } // unwind stack
}
if(argForCarry!=null)
asmgen.out(" plp") // set the carry flag back to correct value
}
private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
// pass parameter via a regular variable (not via registers)
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("unknown dt")
val valueDt = valueIDt.getOr(DataType.UNDEFINED)
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
val varName = asmgen.asmVariableName(sub.scopedname+"."+parameter.value.name)
asmgen.assignExpressionToVariable(value, varName, parameter.value.type, sub)
}
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
// pass argument via a register parameter
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("unknown dt")
val valueDt = valueIDt.getOr(DataType.UNDEFINED)
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
val paramRegister = sub.asmParameterRegisters[parameter.index]
val statusflag = paramRegister.statusflag
val register = paramRegister.registerOrPair
val requiredDt = parameter.value.type
if(requiredDt!=valueDt) {
if(valueDt largerThan requiredDt)
throw AssemblyError("can only convert byte values to word param types")
}
if (statusflag!=null) {
if(requiredDt!=valueDt)
throw AssemblyError("for statusflag, byte value is required")
if (statusflag == Statusflag.Pc) {
// this param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
when(value) {
is NumericLiteralValue -> {
val carrySet = value.number.toInt() != 0
asmgen.out(if(carrySet) " sec" else " clc")
}
is IdentifierReference -> {
val sourceName = asmgen.asmVariableName(value)
asmgen.out("""
pha
lda $sourceName
beq +
sec
bcs ++
+ clc
+ pla
""")
}
else -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.out("""
beq +
sec
bcs ++
+ clc
+""")
}
}
} else throw AssemblyError("can only use Carry as status flag parameter")
}
else {
// via register or register pair
register!!
if(requiredDt largerThan valueDt) {
// we need to sign extend the source, do this via temporary word variable
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_W1", DataType.UBYTE, sub)
asmgen.signExtendVariableLsb("P8ZP_SCRATCH_W1", valueDt)
asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register)
} else {
val target: AsmAssignTarget =
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, parameter.value.type, sub, register = register)
else
AsmAssignTarget.fromRegisters(register, sub, program, asmgen)
val src = if(valueDt in PassByReferenceDatatypes) {
if(value is IdentifierReference) {
val addr = AddressOf(value, Position.DUMMY)
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
}
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
}
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, program.memsizer, Position.DUMMY))
}
}
}
private fun isArgumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
if(argType isAssignableTo paramType)
return true
if(argType in ByteDatatypes && paramType in ByteDatatypes)
return true
if(argType in WordDatatypes && paramType in WordDatatypes)
return true
// we have a special rule for some types.
// strings are assignable to UWORD, for example, and vice versa
if(argType==DataType.STR && paramType==DataType.UWORD)
return true
if(argType==DataType.UWORD && paramType == DataType.STR)
return true
return false
}
}

View File

@ -1,12 +1,12 @@
package prog8.compiler.target.c64.codegen
package prog8.compiler.target.cpu6502.codegen
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.PostIncrDecr
import prog8.ast.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.toHex
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
@ -15,11 +15,11 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
val targetIdent = stmt.target.identifier
val targetMemory = stmt.target.memoryAddress
val targetArrayIdx = stmt.target.arrayindexed
val scope = stmt.definingSubroutine()
val scope = stmt.definingSubroutine
when {
targetIdent!=null -> {
val what = asmgen.asmVariableName(targetIdent)
when (stmt.target.inferType(program).typeOrElse(DataType.STRUCT)) {
when (stmt.target.inferType(program).getOr(DataType.UNDEFINED)) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
in WordDatatypes -> {
if(incr)
@ -65,9 +65,10 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
}
targetArrayIdx!=null -> {
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar)
val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.STRUCT)
if(targetArrayIdx.indexer.indexNum!=null) {
val indexValue = targetArrayIdx.indexer.constIndex()!! * elementDt.memorySize()
val elementDt = targetArrayIdx.inferType(program).getOr(DataType.UNDEFINED)
val constIndex = targetArrayIdx.indexer.constIndex()
if(constIndex!=null) {
val indexValue = constIndex * program.memsizer.memorySize(elementDt)
when(elementDt) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
in WordDatatypes -> {
@ -91,7 +92,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
else
{
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
asmgen.saveRegister(CpuRegister.X, false, scope!!)
asmgen.saveRegisterLocal(CpuRegister.X, scope!!)
asmgen.out(" tax")
when(elementDt) {
in ByteDatatypes -> {
@ -105,7 +106,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
lda $asmArrayvarname,x
bne +
dec $asmArrayvarname+1,x
+ dec $asmArrayvarname
+ dec $asmArrayvarname,x
""")
}
DataType.FLOAT -> {
@ -119,7 +120,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
}
else -> throw AssemblyError("weird array elt dt")
}
asmgen.restoreRegister(CpuRegister.X, false)
asmgen.restoreRegisterLocal(CpuRegister.X)
}
}
}

Some files were not shown because too many files have changed in this diff Show More