Jump to content


  • Content Count

  • Joined

  • Last visited

  • Days Won


dmsc last won the day on July 14 2017

dmsc had the most liked content!

Community Reputation

1,188 Excellent


About dmsc

  • Rank

Profile Information

  • Gender
  • Location
    Viña del Mar, Chile

Recent Profile Visitors

8,258 profile views
  1. Hi! As @Wrathchild explained, FastBasic is designed as a native tool (meaning it runs in the Atari itself), and my goal is to always have the integer IDE using less than 8KB of memory. So, adding an integrated assembler is beyond the scope of the standard language. But in the cross-compiler you already can include external assembly files, there is an example at https://github.com/dmsc/fastbasic/blob/master/compiler/USAGE.md#linking-other-assembly-files Basically, you write your assembly function in a separate (.asm) file and then compile both files together in the fb command line. Have Fun!
  2. Hi! Wow, very good game!! I noticed that the map display was a little slow, so I optimized it a little, from: PROC M FOR R = 0 TO 6 :. map display is 7 rows tall FOR C = 0 TO 6 :. map display is 7 columns wide G = 206 :. character code for player gylph in color white IF C - 3 OR R - 3 :. this is not spot where the player glyph always appears, so get map glyph instead G = PEEK(K + PEEK(M + P - 84 + 27 * R + C) * 3) :. look up entity in map cell, then glyph for entity ENDIF POKE Z + 81 + 20 * R + C, G :. put glyph on screen NEXT NEXT ENDPROC Into: PROC M FOR R = 0 TO 6 :. map display is 7 rows tall O = M + P - 84 + 27 * R ' Precompute map source A = Z + 81 + 20 * R ' and screen destination FOR C = 0 TO 6 :. map display is 7 columns wide G = 206 :. character code for player gylph in color white IF C - 3 OR R - 3 :. this is not spot where the player glyph always appears, so get map glyph instead G = PEEK(K + PEEK(O + C) * 3) :. look up entity in map cell, then glyph for entity ENDIF POKE A + C, G :. put glyph on screen NEXT NEXT ENDPROC IMHO, the difference is noticeable, and the source is still at 10 lines. Also, for the formatted source, I prefer to use the ' character to comment, as this can be used without the ":" before, IMHO the source looks slightly cleaner, but this is obviously your call. Have Fun!
  3. Hi! First, from a theoretical point, if something is possible in BASIC, it is also possible in assembler, as Basic is written in assembler Now, the 6502 does not have a specific instruction to do that: call a subroutine to an address from a register or a memory location. The standard solution to that in computer sciences is called a "trampoline": You write a subroutine that jumps to the correct location and you call this subroutine (the trampoline) instead. Two possible trampolines are: 1.- Code that is "patched" at runtime with the address to jump to (this is how a trampoline is usually written in many computer architectures). Easiest is to reserve three instead of two location for the address: ; Call our fucntion via the trampoline lda #<procedure1 sta target lda #>procedure1 sta target+1 jsr trampoline ; ........ ; more code here.... rts trampoline: byte $4c ; $4C is the "JMP" instruction target: byte $00 byte $00 The advantage of this solution is that if the address to call (the value of "X" in your example) does not change a lot, this is fast. On most current operating-systems this king of trampolines is used a lot to call to operating-system code (or code from other modules), as you don't know the address of the target until you load it in memory, so you create one trampoline for each function that you want to call. And, if you store the entire trampoline (the 3 bytes) in zero page, the loading of the address is also faster and smaller. 2.- Using the 6502 "indirect jump" instruction. This instruction jumps to an address stored at a memory location, so it looks like: ; Call our fucntion via the trampoline lda #<procedure1 sta target lda #>procedure1 sta target+1 jsr trampoline ; ........ ; more code here.... rts trampoline: jmp (target) ; Indirect jump - go to the address stored in target and target+1 target: byte $00 byte $00 As you see, here the trampoline is 2 bytes bigger, and also 2 cycles slower (as the 6502 needs to read two more bytes). If you store the target in zero-page location, then the code that set a new address is 2 bytes smaller and 2 cycles faster, but only if you set the target before each call. So, this solution is only good if the target address is already set (for example, as part of the operating-system variables) as you don't need to load the address to the trampoline before using. Have Fun!
  4. Hi! You can use the "E_EOL" rule, this returns true if you already consumed all the input line (up until a end-of-line or a ":"). But normally this is not necessary, as the parser has another feature: if on the last line of a rule you write the special word "pass" then the rule returns true if nothing was matched, making the whole rule optional. This allows writing rules that consume as much space as needed, in my example above you simply replace the "NO" with pass: MY_RULE: "WORD" MY_RULE The above is exactly like the regular expression "\(\s*WORD\)*" . FastBasic parser is based on PEG, and you can express any regular-expression as a PEG, but not any PEG can be expressed as a regular-expression. Yes, you need three rules. You only missed a terminator (as written, your grammar only parses infinite inputs ), to terminate before the "," you need to add a "pass" to the RULE3. Yes! So, adding the comma needs an extra rule. Each new rule is: 2 bytes for the rule-address in the parsing tables, and at least two bytes for the rule bytecode - one bytecode to terminate the first line and one bytecode to terminate the rule. And you need one byte to call the rule, so the total is 5 bytes for adding a rule. But there are two other problems: - There is a limit on 127 rules in the parser. Currently the floating-point parser uses 113 rules, the integer-only parser uses 103. So, I still have 14 rules left. - Each additional rule makes the parsing slower and uses 4 bytes of parsing stack (but only when the rule is used). This is specially important on rules that parse a list of values, as you are calling to the same rule for each new value. So, with the first example, each use of the "WSYNC" word calls two rules, using 8 bytes of stack, so the limit is about 30 "WSYNC" words I like explaining the FastBasic parser, I hope someone could help by optimizing the rules - I do know that there is space for optimizations there! Have Fun!
  5. Hi! For the parser, yes, it is different. The structure of the parser does not include spaces, because those are optional. For example, if you have a rule that says: MY_RULE: "WORD" MY_RULE "NO" The generated parser will: - For the call of MY_RULE, - Skip any white spaces on the input, - Push the current position in the input, then: - Check if the character 'W', 'O', 'R' and 'D' are present in the input (advancing the position), if true call to MY_RULE, if returns true, keep the new position and state and return true from this rule. - Return to the saved position, then: - Check if the character 'N' and 'O' are present in the input, if true keeps the current position and return true from this rule - None matched, restore saved position (pop from stack) and return false. The main point on the above is, with that construct, any of this examples matches: - NO - WORD NO - WORDNO - WORD WORD NO - WORDWORDWORD NO But, this does not matches: - N O - WO RDNO I hope all makes sense. Based on the example above, just try to write a parser that matches on this input: - DLI WSYNC , WSYNC,WSYNC How many rules you need? Yes, the parser just is not that smart Have Fun!
  6. Hi! Well, it is almost the same as DLI SET half = $66 INTO 710, WAIT WAIT WAIT WAIT $84 INTO 710 And you probably don't realize, but the problem is not the "," - it is easy to add the parsing of a comma to the "WAIT" word, the problem is the *spaces* between the WAIT and the ",". Currently, the only way to skip spaces in the parser is to split a rule into multiple rules, spaces are skipped always on the start of any rule, you can't specify skipping spaces on arbitrary locations. This is the reason that there is a rule "EQUAL" that just parses an "=" sign. I thought of adding a bytecode to the parser to skip white spaces, but in my earlier tests it was slower and bigger. Perhaps now that the parser has mucho more states it makes sense again. Not currently. To make that work you would need to implement a routine that retrieves the number from the already parser program (as the "parse number" command writes the result to the program stream) and then manually emit as many "STA WSYNC" as needed. If you have any suggestions, you can also comment in the github issues, so I don't forget about it. Have Fun!
  7. Hi! Thanks for the ideas. I created an issue on github to keep track of this enhancements: https://github.com/dmsc/fastbasic/issues/21 , https://github.com/dmsc/fastbasic/issues/22 and https://github.com/dmsc/fastbasic/issues/23 Have Fun!
  8. Hi! Here it works: If it does not work for you, fill a bug report over github at https://github.com/atari800/atari800/issues/ , specifying your OS and settings. Have Fun!
  9. Hi! First, make sure you are on NTSC and not PAL: Then, on Display Settings, select "Video artifacts": And then yuo have it: Have Fun!
  10. Hi! A little more "idiomatic" C : #include <stdio.h> #include <atari.h> #pragma static-locals(on) #define p 10000 #define r 16129 void main(void) { register unsigned int x, y; unsigned int stop, i, b = 0; register unsigned char n; n = OS.rtclok[2]; while (OS.rtclok[2] == n) { ; } printf("\nMonte-Carlo PI cc65 V2.18\n"); OS.rtclok[1] = 0; OS.rtclok[2] = 0; for (i = 0 ; i < p; ++i) { n = (POKEY_READ.random | 128) ^ 128; x = n * n; n = (POKEY_READ.random | 128) ^ 128; y = n * n; if ((x + y) <= r) ++b; } b *= 4; stop = OS.rtclok[2] + 256 * OS.rtclok[1]; printf("%u.%04u\n%u ticks\n", b/10000,b%10000, stop); for (;;); } Also, writing (n = POKEY_READ.random & 0x7F) would be a little faster. Have Fun!
  11. Hi! First, thank to all for the suggestions. Yes, it is somewhat restrictive, but I designed it to be as simple as possible, it is BASIC after all Ah, you already saw my little trick I discovered that trick by trying to implement it using VBI and discovered it became complicated really fast..., then reading the sources of ALtirra OS there phaeron says that Pole Position depends on it, so that gives a precedent. As you said, if you use it as intended it does not causes problems, simply the colors would be wrong. Read accesses would be out of bounds, but normally still in RAM. Yes. My first implementation of multiple DLIs used this, but it was too much code, as a VBI would need to be installed on DLI activation - but only if it was not already installed - and the VBI needs to be removed on program termination. This made the code a lot bigger and complicated, so I finally discarded the idea. Yes, that would be nice. Currently is not easy to support reading from a word array in a DLI, as there is only the X register available, you would need to add a "LDA COLRSH : ASL : TAX ", and then the X register would need to be reloaded if you access word arrays again, this is slow and would need a lot of code in the IDE. And finally, using the index to write into the DLI vector would be dangerous as the index could go out of bounds, as you said above. Adding this it would be possible. IMHO, I prefer the syntax with "DLI", as the "INTO" writes only a byte in all other cases. Very good!! You can simplify the "dli_low(0) = ADR(d3b) mod 256" into "dli_low(0) = ADR(d3b)", as you are writing to a byte array and the high byte is discarded. I like that syntax. I could reuse most of the DLI implementation to do the VBI one. What I would try to resolve is that by including a VBI, all FastBasic programs would need to include VBI initialization and cleanup, just in case the program uses it. One question: ¿should VBI vector the immediate or the deferred VBI? You can already do this by writing "$00 INTO $D40A", but I understand the using "WSYNC" would mean a little less code. But, I think I would prefer another word, more clear to the BASIC context, like "NEXT": DLI SET graded_dli = $04 INTO $D017, DLI = NEXT $06 INTO $D017, DLI = NEXT $08 INTO $D017, DLI = NEXT $0C INTO $D017 This is easy to implement, as the parser can transform NEXT directly to "STA WSYNC", and you notice that I removed the comma after the NEXT to make easier to parse. To understand that the DLI support inf FastBasic is extremely simple, look at the full changes to the sources the implement it: https://github.com/dmsc/fastbasic/compare/8ea6ad8...37bf4ca It is only one change to the parser file, adding 29 lines total. The line 669 says, when parsing a statement, if the parser founds "DLI" (abbreviated "DL."), parse a "DLI_COMMAND". Then, line 580 is the rule to parse a DLI_COMMAND, with four options: first if there is a "SET", parse the initial part of the DLI, generating the starting code; if there is an "=" sign, parse a continuation, if there is a LABEL, activate the DLI and finally in any other case, deactivate the DLI. Rule "DLI_LDA" defines the part of the DLI that produces a "LDA" (before the word "INTO"), "DLI_STA" defines the second part, and "DLI_CODES" is the comma separated list of DLI_LDA and DLI_STA. Adding the "NEXT" token would mean adding another option to "DLI_LDA", one line with: "Next" emit { 141, &WSYNC } DLI_LDA This detects the word "NEXT", emits the code for "STA WSYNC" and then calls DLI_LDA again to parse the rest of the statement. This is 9 extra bytes in the IDE. Have Fun!
  12. Hi! No, but you don't need that, as the DLI takes values from arrays you simply write different values on each call. If you don't need to alter one register, you simply write the same value each time. The DLi counter is already reset on the VBI. Have Fun!
  13. Hi! FastBasic does this, there are tokens for "0", "1" and for numbers less than 256, so those are smaller and faster. Well, this is why FastBasic does not have GOTOs . In fact, the FastBasic bytecode does have a JUMP instruction, used internally for the loops, EXIT and the IF/THEN, but the parameter is the address of the target instruction so the jump is really fast. By replacing GOTO targets in other BASICs with addresses on first RUN you could achieve a big speedup. This exists in TurboBasic, the PROC/EXEC and GO# use labels that are stored in the same variable table, and the target address is populated on run. In FastBasic, labels are different than variables, so the parser has two name tables. Also, FastBasic has a table of label addresses and where each label is used, this is used to patch the JUMP and CALL addresses with the target address when know. And finally, for loops FastBasic has a stack of loop addresses, with the type of loop and the address of loop start/end. But one of the differences of FastBasic with other interpreted basics is that all that tables and stacks are only maintained during parsing, on running all that data is not needed as all the addresses are already stored with the bytecode. There is no reason why the table could be bigger, and I doubt the number of branch destinations are that much. You could have a look at my turbo-basic parsing tool, at https://github.com/dmsc/tbxl-parser , that tool can parse all TurboBasic XL syntax and optionally do some useful transformations: - delete unused variables, - delete unused line numbers (line numbers that are not referenced), - replace constants with variables storing the number (for example, all "0" are replaced by %0) - remove extra parentheses and other unneeded tokens. At end of parsing the tool writes the number of variables and lines. For FastBasic, I have some real profiling tools, I run test programs and the profile counts the number of tokens executed and the time spent executing each token, this has been very valuable to analyze and improve the speed of the interpreter. Have Fun!
  14. I finally managed to finish the DLI work, so here is the new FastBasic version, ready for the 2020 tenliners! You can now create a DLI with a simple syntax, capable of changing registers at multiple locations in the screen. The full documentation is in the manual at https://github.com/dmsc/fastbasic/blob/master/manual.md#display-list-interrupts , but here is a little sample of the DLI support, showing how to multiplex two P/M to create 4 moving two-color sprites: ' Player shapes DATA p1() BYTE = $E7,$81,$81,$E7 DATA p2() BYTE = $18,$3C,$3C,$18 ' Players horizontal positions DATA pos() BYTE = $40,$60,$80,$A0 ' Players colors DATA c1() BYTE = $28,$88,$C8,$08 DATA c2() BYTE = $2E,$80,$CE,$06 ' Our DLI writes the position and ' colors to Player 1 and Player 2 DLI SET d3 = pos INTO $D000, pos INTO $D001, DLI = c1 INTO $D012, c2 INTO $D013 ' Setup screen GRAPHICS 0 : PMGRAPHICS 2 ' Setup our 4 DLI and Players FOR I = 8 TO 20 STEP 4 POKE DPEEK(560) + I, 130 MOVE ADR(p1), PMADR(0)+I*4+5,4 MOVE ADR(p2), PMADR(1)+I*4+5,4 NEXT ' Activate DLI DLI d3 ? "Press a Key" ' Move objects! REPEAT PAUSE 0 pos(0) = pos(0) + 2 pos(1) = pos(1) + 1 pos(2) = pos(2) - 1 pos(3) = pos(3) - 2 UNTIL KEY() ' Disable DLI and ends DLI Attached is the resulting XEX, this is the resulting screen: The new release is over github, download at: https://github.com/dmsc/fastbasic/releases/tag/v4.3 Have Fun! dlitest.xex
  15. Hi! Here you changed the code, using "sqr_count" as the upper limit instead of the full size is misleading, as all other programs do the complete loop. So, you results sadly are not comparable. Have Fun!
  • Create New...