diff --git a/README.md b/README.md index e0660fa7d..6833131ef 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ What does Prog8 provide? ------------------------ - all advantages of a higher level language over having to write assembly code manually -- programs run very fast because compilation to native machine code. It's possible to write games purely in Prog8, and even certain raster interrupt 'demoscene' effects. +- programs run very fast because compilation to native machine code - modularity, symbol scoping, subroutines - various data types other than just bytes (16-bit words, floats, strings) - floating point math is supported if the target system provides floating point library routines (C64 and Cx16 both do) @@ -71,7 +71,7 @@ What does Prog8 provide? - convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses - 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) +- encode strings and characters into petscii or screencodes or even other encodings, as desired (C64/Cx16) *Rapid edit-compile-run-debug cycle:* diff --git a/codeCore/src/prog8/code/core/CompilationOptions.kt b/codeCore/src/prog8/code/core/CompilationOptions.kt index 096ad8f6b..d54b81033 100644 --- a/codeCore/src/prog8/code/core/CompilationOptions.kt +++ b/codeCore/src/prog8/code/core/CompilationOptions.kt @@ -20,6 +20,7 @@ class CompilationOptions(val output: OutputType, var asmListfile: Boolean = false, var includeSourcelines: Boolean = false, var dumpVariables: Boolean = false, + var dumpSymbols: Boolean = false, var experimentalCodegen: Boolean = false, var varsHighBank: Int? = null, var varsGolden: Boolean = false, diff --git a/compiler/src/prog8/CompilerMain.kt b/compiler/src/prog8/CompilerMain.kt index 65b282fdb..7e58ef803 100644 --- a/compiler/src/prog8/CompilerMain.kt +++ b/compiler/src/prog8/CompilerMain.kt @@ -43,6 +43,7 @@ private fun compileMain(args: Array): Boolean { val startEmulator2 by cli.option(ArgType.Boolean, fullName = "emu2", description = "auto-start alternative emulator after successful compilation") val experimentalCodegen by cli.option(ArgType.Boolean, fullName = "expericodegen", description = "use experimental/alternative codegen") val dumpVariables by cli.option(ArgType.Boolean, fullName = "dumpvars", description = "print a dump of the variables in the program") + val dumpSymbols by cli.option(ArgType.Boolean, fullName = "dumpsymbols", description = "print a dump of the variables + subroutine definitions") val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code") val noStrictBool by cli.option(ArgType.Boolean, fullName = "nostrictbool", description = "allow implicit conversions between bool and bytes") val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform code optimizations") @@ -155,6 +156,7 @@ private fun compileMain(args: Array): Boolean { includeSourcelines == true, experimentalCodegen == true, dumpVariables == true, + dumpSymbols == true, varsHighBank, varsGolden == true, slabsHighBank, @@ -235,6 +237,7 @@ private fun compileMain(args: Array): Boolean { includeSourcelines == true, experimentalCodegen == true, dumpVariables == true, + dumpSymbols==true, varsHighBank, varsGolden == true, slabsHighBank, diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index a5b2a9e83..70129fe06 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -7,6 +7,7 @@ import prog8.ast.base.AstException import prog8.ast.expressions.Expression import prog8.ast.expressions.NumericLiteral import prog8.ast.printProgram +import prog8.ast.printSymbols import prog8.ast.statements.Directive import prog8.code.SymbolTableMaker import prog8.code.ast.PtProgram @@ -22,6 +23,7 @@ import java.nio.file.Path import kotlin.io.path.Path import kotlin.io.path.nameWithoutExtension import kotlin.math.round +import kotlin.system.exitProcess import kotlin.system.measureTimeMillis @@ -39,6 +41,7 @@ class CompilerArguments(val filepath: Path, val includeSourcelines: Boolean, val experimentalCodegen: Boolean, val dumpVariables: Boolean, + val dumpSymbols: Boolean, val varsHighBank: Int?, val varsGolden: Boolean, val slabsHighBank: Int?, @@ -71,7 +74,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? { var compilationOptions: CompilationOptions var ast: PtProgram? = null var resultingProgram: Program? = null - var importedFiles: List = emptyList() + var importedFiles: List try { val totalTime = measureTimeMillis { @@ -86,6 +89,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? { includeSourcelines = args.includeSourcelines experimentalCodegen = args.experimentalCodegen dumpVariables = args.dumpVariables + dumpSymbols = args.dumpSymbols breakpointCpuInstruction = args.breakpointCpuInstruction varsHighBank = args.varsHighBank varsGolden = args.varsGolden @@ -402,6 +406,12 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) { program.preprocessAst(errors, compilerOptions) + + if(compilerOptions.dumpSymbols) { + printSymbols(program) + exitProcess(0) + } + program.checkIdentifiers(errors, compilerOptions) errors.report() program.charLiteralsToUByteLiterals(compilerOptions.compTarget, errors) diff --git a/compiler/test/TestCompilerOnExamples.kt b/compiler/test/TestCompilerOnExamples.kt index 31fb06fa6..78707b09c 100644 --- a/compiler/test/TestCompilerOnExamples.kt +++ b/compiler/test/TestCompilerOnExamples.kt @@ -33,6 +33,7 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat includeSourcelines = false, experimentalCodegen = false, dumpVariables = false, + dumpSymbols = false, varsHighBank = null, varsGolden = false, slabsHighBank = null, diff --git a/compiler/test/TestCompilerOptionLibdirs.kt b/compiler/test/TestCompilerOptionLibdirs.kt index 6c9baca0b..f1ee2b014 100644 --- a/compiler/test/TestCompilerOptionLibdirs.kt +++ b/compiler/test/TestCompilerOptionLibdirs.kt @@ -31,6 +31,7 @@ class TestCompilerOptionSourcedirs: FunSpec({ includeSourcelines = false, experimentalCodegen = false, dumpVariables = false, + dumpSymbols = false, varsHighBank = null, varsGolden = false, slabsHighBank = null, diff --git a/compiler/test/ast/TestAstToSourceText.kt b/compiler/test/ast/TestAstToSourceText.kt index 76a7dbfed..26ae9ca2e 100644 --- a/compiler/test/ast/TestAstToSourceText.kt +++ b/compiler/test/ast/TestAstToSourceText.kt @@ -22,7 +22,7 @@ class TestAstToSourceText: AnnotationSpec() { .addModule(module) var generatedText = "" - val it = AstToSourceTextConverter({ str -> generatedText += str }, program) + val it = AstToSourceTextConverter({ str -> generatedText += str }, program, true) it.visit(program) return generatedText diff --git a/compiler/test/helpers/compileXyz.kt b/compiler/test/helpers/compileXyz.kt index 2c03b0602..d0982f958 100644 --- a/compiler/test/helpers/compileXyz.kt +++ b/compiler/test/helpers/compileXyz.kt @@ -30,6 +30,7 @@ internal fun compileFile( includeSourcelines = false, experimentalCodegen = false, dumpVariables = false, + dumpSymbols = false, varsHighBank = null, varsGolden = false, slabsHighBank = null, diff --git a/compilerAst/src/prog8/ast/AstToSourceTextConverter.kt b/compilerAst/src/prog8/ast/AstToSourceTextConverter.kt index 71d44805f..692dd9cac 100644 --- a/compilerAst/src/prog8/ast/AstToSourceTextConverter.kt +++ b/compilerAst/src/prog8/ast/AstToSourceTextConverter.kt @@ -8,7 +8,7 @@ import prog8.code.core.* fun printProgram(program: Program) { println() - val printer = AstToSourceTextConverter(::print, program) + val printer = AstToSourceTextConverter(::print, program, true) printer.visit(program) println() } @@ -18,7 +18,7 @@ fun printProgram(program: Program) { * Produces Prog8 source text from a [Program] (AST node), * passing it as a String to the specified receiver function. */ -class AstToSourceTextConverter(val output: (text: String) -> Unit, val program: Program): IAstVisitor { +class AstToSourceTextConverter(val output: (text: String) -> Unit, val program: Program, val skipLibraries: Boolean): IAstVisitor { private var scopelevel = 0 private fun indent(s: String) = " ".repeat(scopelevel) + s @@ -33,7 +33,7 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program: } override fun visit(module: Module) { - if(!module.isLibrary) { + if(!module.isLibrary || !skipLibraries) { outputln("; ----------- module: ${module.name} -----------") super.visit(module) } diff --git a/compilerAst/src/prog8/ast/SymbolPrinter.kt b/compilerAst/src/prog8/ast/SymbolPrinter.kt new file mode 100644 index 000000000..d9a8f2c88 --- /dev/null +++ b/compilerAst/src/prog8/ast/SymbolPrinter.kt @@ -0,0 +1,153 @@ +package prog8.ast + +import prog8.ast.statements.* +import prog8.ast.walk.IAstVisitor +import prog8.code.core.DataType +import prog8.code.core.ZeropageWish +import prog8.code.core.toHex + + +fun printSymbols(program: Program) { + println() + val printer = SymbolPrinter(::print, program, false) + printer.visit(program) + println() +} + + +class SymbolPrinter(val output: (text: String) -> Unit, val program: Program, val skipLibraries: Boolean): IAstVisitor { + private fun outputln(text: String) = output(text + "\n") + + override fun visit(module: Module) { + if(!module.isLibrary || !skipLibraries) { + if(module.source.isFromFilesystem || module.source.isFromResources) { + outputln("MODULE FILE: ${module.source.origin}") + super.visit(module) + output("\n") + } + } + } + + override fun visit(block: Block) { + outputln("${block.name} {") + val (vars, subs) = block.statements.filter{ it is Subroutine || it is VarDecl }.partition { it is VarDecl } + for(variable in vars.sortedBy { (it as VarDecl).name }) { + output(" ") + variable.accept(this) + } + for(subroutine in subs.sortedBy { (it as Subroutine).name }) { + output(" ") + subroutine.accept(this) + } + outputln("}\n") + } + + private fun datatypeString(dt: DataType): String { + return when (dt) { + DataType.BOOL -> "bool" + DataType.UBYTE -> "ubyte" + DataType.BYTE -> "byte" + DataType.UWORD -> "uword" + DataType.WORD -> "word" + DataType.LONG -> "long" + DataType.FLOAT -> "float" + DataType.STR -> "str" + DataType.ARRAY_UB -> "ubyte[" + DataType.ARRAY_B -> "byte[" + DataType.ARRAY_UW -> "uword[" + DataType.ARRAY_W -> "word[" + DataType.ARRAY_F -> "float[" + DataType.ARRAY_BOOL -> "bool[" + DataType.ARRAY_UW_SPLIT -> "@split uword[" + DataType.ARRAY_W_SPLIT -> "@split word[" + DataType.UNDEFINED -> throw IllegalArgumentException("wrong dt") + } + } + + override fun visit(decl: VarDecl) { + if(decl.origin==VarDeclOrigin.SUBROUTINEPARAM) + return + + when(decl.type) { + VarDeclType.VAR -> {} + VarDeclType.CONST -> output("const ") + VarDeclType.MEMORY -> output("&") + } + + output(datatypeString(decl.datatype)) + if(decl.arraysize!=null) { + decl.arraysize!!.indexExpr.accept(this) + } + if(decl.isArray) + output("]") + + if(decl.zeropage == ZeropageWish.REQUIRE_ZEROPAGE) + output(" @requirezp") + else if(decl.zeropage == ZeropageWish.PREFER_ZEROPAGE) + output(" @zp") + if(decl.sharedWithAsm) + output(" @shared") + + output(" ") + if(decl.names.size>1) + output(decl.names.joinToString(prefix=" ")) + else + output(" ${decl.name} ") + output("\n") + } + + override fun visit(subroutine: Subroutine) { + if(subroutine.isAsmSubroutine) { + output("${subroutine.name} (") + for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) { + val reg = + when { + param.second.registerOrPair!=null -> param.second.registerOrPair.toString() + param.second.statusflag!=null -> param.second.statusflag.toString() + else -> "?????" + } + output("${datatypeString(param.first.type)} ${param.first.name} @$reg") + if(param.first!==subroutine.parameters.last()) + output(", ") + } + } + else { + output("${subroutine.name} (") + for(param in subroutine.parameters) { + output("${datatypeString(param.type)} ${param.name}") + if(param!==subroutine.parameters.last()) + output(", ") + } + } + output(") ") + if(subroutine.asmClobbers.isNotEmpty()) { + output("-> clobbers (") + val regs = subroutine.asmClobbers.toList().sorted() + for(r in regs) { + output(r.toString()) + if(r!==regs.last()) + output(",") + } + output(") ") + } + if(subroutine.returntypes.any()) { + if(subroutine.asmReturnvaluesRegisters.isNotEmpty()) { + val rts = subroutine.returntypes.zip(subroutine.asmReturnvaluesRegisters).joinToString(", ") { + val dtstr = datatypeString(it.first) + if(it.second.registerOrPair!=null) + "$dtstr @${it.second.registerOrPair}" + else + "$dtstr @${it.second.statusflag}" + } + output("-> $rts ") + } else { + val rts = subroutine.returntypes.joinToString(", ") { datatypeString(it) } + output("-> $rts ") + } + } + if(subroutine.asmAddress!=null) + output("= ${subroutine.asmAddress.toHex()}") + + output("\n") + } +} diff --git a/docs/source/index.rst b/docs/source/index.rst index 26839b40c..83ce68e6c 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -26,12 +26,13 @@ You can compile programs for various machines with this CPU: * Commodore PET (limited support) * Atari 800 XL (limited support) +The source code is on github: https://github.com/irmen/prog8.git -Prog8 is copyright © Irmen de Jong (irmen@razorvine.net | http://www.razorvine.net). -The project is on github: https://github.com/irmen/prog8.git Software License ^^^^^^^^^^^^^^^^ +Prog8 is copyright © Irmen de Jong (irmen@razorvine.net | http://www.razorvine.net). + This is free software, as defined in the GNU GPL 3.0 (https://www.gnu.org/licenses/gpl.html) *Exception:* All output files generated by the compiler (intermediary files and compiled binary programs) are excluded from this particular license: you can do with those *whatever you want*. @@ -65,36 +66,38 @@ you can help me out a little bit over at https://ko-fi.com/irmen . :alt: Chess program for the X16 +Features +-------- -Language features ------------------ - -- It is a cross-compiler running on modern machines (Linux, MacOS, Windows, ...) -- Programs run very fast because compilation to native machine code. It's possible to write games purely in Prog8, and even certain raster interrupt 'demoscene' effects. -- Provides a very convenient edit/compile/run cycle by being able to directly launch +- it is a cross-compiler running on modern machines (Linux, MacOS, Windows, ...) +- the compiled programs run very fast, because compilation to highly efficient native machine code. +- Provides a convenient and fast edit/compile/run cycle by being able to directly launch the compiled program in an emulator and provide debugging information to this emulator. -- Based on simple and familiar imperative structured programming (it looks like a mix of C and Python) -- Modular programming and scoping via modules, code blocks, and subroutines. -- No need for forward declarations. +- the language looks like a mix of Python and C so should be quite easy to learn +- Modular programming, scoping via modules, code blocks, and subroutines. No need for forward declarations. - Provide high level programming constructs but at the same time stay close to the metal; still able to directly use memory addresses and ROM subroutines, and inline assembly to have full control when every register, cycle or byte matters -- Subroutines with parameters and return values +- Subroutines with parameters and return values of various types - Complex nested expressions are possible -- Variables are all allocated statically -- Conditional branches for status flags that map 1:1 to processor branch instructions +- Variables are all allocated statically, no memory allocator overhead +- Conditional branches for status flags that map 1:1 to processor branch instructions for optimal efficiency - ``when`` statement to avoid if-else chains - ``in`` expression for concise and efficient multi-value/containment test - Several powerful built-in functions, such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``, ``sort`` and ``reverse`` - Variable data types include signed and unsigned bytes and words, arrays, strings. +- Various powerful built-in libraries to do I/O, number conversions, graphics and more - Floating point math is supported on select compiler targets. +- Easy and highly efficient integration with external subroutines and ROM routines on the target systems. - 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. +- Encode strings and characters into petscii or screencodes or even other encodings, as desired (C64/Cx16) - Identifiers can contain Unicode Letters, so ``knäckebröd``, ``приблизительно``, ``見せしめ`` and ``π`` are all valid identifiers. -- Advanced code optimizations, such as const-folding (zero-allocation constants that are optimized away in expressions), expression and statement simplifications/rewriting. -- Programs can be run multiple times without reloading because of automatic variable (re)initializations. -- Supports the sixteen 'virtual' 16-bit registers R0 to R15 as defined on the Commander X16, also on the other machines. -- Support for low level system features such as Vera Fx hardware word multiplication on the Commander X16 -- If you only use standard Kernal and core prog8 library routines, it is sometimes possible to compile the *exact same program* for different machines (just change the compilation target flag) +- Advanced code optimizations to make the resulting program smaller and faster +- Programs can be restarted after exiting (i.e. run them multiple times without having to reload everything), due to automatic variable (re)initializations. +- Supports the sixteen 'virtual' 16-bit registers R0 to R15 as defined on the Commander X16. These are also available on the other compilation targets! +- On the Commander X16: Support for low level system features such as Vera Fx, which includes 16x16 bits multiplication in hardware and fast memory copy and fill. +- Many library routines are available across compiler targets. This means that as long as you only use standard Kernal + and core prog8 library routines, it is sometimes possible to compile the *exact same program* for different machines (just change the compilation target flag). Code example diff --git a/examples/test.p8 b/examples/test.p8 index 57f38aa19..b745d7f55 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,15 +1,28 @@ +%import bmx +%import diskio +%import emudbg +%import floats +%import gfx2 +%import graphics +%import monogfx +%import palette +%import psg +%import sprites +%import syslib %import textio +%import verafx +%import conv +%import cx16logo +%import math +%import prog8_lib +%import string +%import test_stack + %zeropage basicsafe %option no_sysinit main { sub start() { - ubyte @shared x,y = multi() - } - - asmsub multi() -> ubyte @A, ubyte @Y { - %asm {{ - rts - }} } } +