Jump to content
IGNORED

FastBasic 4.3 - With DLI!


dmsc

Recommended Posts

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:

 

image.thumb.png.f05d38c14d37b51597ea210a981c751d.png

 

The new release is over github, download at: https://github.com/dmsc/fastbasic/releases/tag/v4.3

 

Have Fun!

 

dlitest.xex

  • Like 17
  • Thanks 3
Link to comment
Share on other sites

Hi!

2 hours ago, Wrathchild said:

Can DLI x be used within another DLI to chain them?

If so I think you'll need a similar VBI statement within which you'd use a DLI statement to reset the 1st interrupt routine? 

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!

 

Link to comment
Share on other sites

6 hours ago, dmsc said:

No, but you don't need that, as the DLI takes values from arrays you simply write different values on each call.

That makes it quite a restrictive model and a not too-realistic use-case of DLIs, i.e. you'll potentially need to limit the number of changes due to time criticality. 

 

6 hours ago, dmsc said:

The DLi counter is already reset on the VBI.

 

The 'DLi counter' you are exploiting is the COLRSH register ($4F).

Once the attract mode is triggered this is given a value from middle clock register ($13) during the VBI. (e.g. "bx db(COLRSH)>4")
Whilst in most cases just colours or sprite positions are being set and so at most will then to stop being animated and give them different colours, this is dangerous as it means that access to DATA BYTE arrays are potentially out of bounds.

 

As the DLI vector isn't refresh by the OS during the Vblank (e.g. checked with "bx write=$200") this means that the last DLI is responsible for setting the first DLIs address.

During development this often can cause the effect of 'rolling' when the number of interrupts in the Dlist don't match the number in your chain.

Hence setting the first DLI in the VBlank is a good help towards helping to rectify that.

 

I've experimented and demonstrate what I mean in the attached modified example.


From this I would say it would be nice if DATA WORD support could be added, e.g.

' Dli addresses
DATA dli_adr() WORD = 0,0,0

' Our DLI writes the position and
' colors to Player 1 and Player 2
DLI SET d3a = pos INTO $D000, pos INTO $D001,
DLI        = $28 INTO $D012, $2E INTO $D013,
DLI        = dli_adr INTO $200

DLI SET d3b = pos INTO $D000, pos INTO $D001,
DLI        = $88 INTO $D012,
DLI        = dli_adr INTO $200

DLI SET d3c = pos INTO $D000, pos INTO $D001,
DLI        = $CE INTO $D013,
DLI        = dli_adr INTO $200

dli_adr(0) = ADR(d3b)
dli_adr(1) = ADR(d3c)
dli_adr(2) = ADR(d3a)

 

So initially I'd thought that the last line of a dli could be:

DLI = DLI d3b

but this isn't possible and so considering if the above was working then the dli_adr could be eliminated by using:

DLI = d3b INTO $200

but as I understand it the lack of forward reference inhibits writing it that way and so the array is suffices.

Which means that actually it is reusable between different screens such as a title page and game screen by applying the addresses of different DLI definitions.

 

Keep up the good work!

 

altdli.bas altdli.xex

Edited by Wrathchild
Elaboration on COLRSH use
Link to comment
Share on other sites

Current implementation of DLIs in FastBasic is a very simple one. It is not managed as a chain of routines, it just enables a single routine that can be called multiple times (many scan lines), so it always changes the same registers, but the values might be constants or obtained from DATA arrays.

 

Of course you can define multiple DLI routines in FastBasic (DLI SET), but you can enable only one at a time, or disable them at all.

 

I was also affraid of the use of COLRSH as the counter when in attract mode for the same reason you are exposing, but I forced that situation in the game I'm working on and I couldn't get a strange behavior.

 

To use complex DLI routines with chains and such, you can always enable them with a custom VBI embedding them as it has always been done in any BASIC flavor (with POKEs ;-)).

 

But it is very interesting what you are proposing. I think that chains could be done using current implementation in this way:

 

' Dli addresses
DATA dli_adr_l() BYTE = 0,0,0
DATA dli_adr_h() BYTE = 0,0,0

' Our DLI writes the position and
' colors to Player 1 and Player 2
DLI SET d3a = pos INTO $D000, pos INTO $D001,
DLI        = $28 INTO $D012, $2E INTO $D013,
DLI        = dli_adr_l INTO $200, dli_adr_h INTO $201

DLI SET d3b = pos INTO $D000, pos INTO $D001,
DLI        = $88 INTO $D012,
DLI        = dli_adr_l INTO $200, dli_adr_h INTO $201

DLI SET d3c = pos INTO $D000, pos INTO $D001,
DLI        = $CE INTO $D013,
DLI        = dli_adr_l INTO $200, dli_adr_h INTO $201

dli_adr_l(0) = ADR(d3b) mod 256
dli_adr_h(0) = ADR(d3b) / 256
dli_adr_l(1) = ADR(d3c) mod 256
dli_adr_h(1) = ADR(d3c) / 256
dli_adr_l(2) = ADR(d3a) mod 256
dli_adr_h(2) = ADR(d3a) / 256

Then, you can change very different system registers in each DLI, but I hope that the time for that will be enougth.

 

UPDATE: I just downloaded and opened your example, and got almost the same code than I wrote in this post. ?

 

Edited by vitoco
Update...
Link to comment
Share on other sites

I think the ideal could be something like this - as if you define the routines in reverse order then you can reference them, and the last does not need to set the first if this is done in the VBI, e.g.

DLI SET dli3 = pos INTO $D000, pos INTO $D001,
DLI        = $CE INTO $D013

DLI SET dli2 = pos INTO $D000, pos INTO $D001,
DLI        = $88 INTO $D012,
DLI        = DLI dli3

DLI SET dli1 = pos INTO $D000, pos INTO $D001,
DLI        = $28 INTO $D012, $2E INTO $D013,
DLI        = DLI dli2

VBI SET vb1 = DLI dli1

...

VBI vb1

As an example, the VBI can then also be used to call a linked RMT player routine

Link to comment
Share on other sites

I'd forgotten to mention another idea, let the user state WSYNC as in the example you are in Gr.0 and even with a DLI stated each line then you can only update registers every 8 lines.

 

DLI SET graded_dli = $04 INTO $D017,
DLI        = WSYNC, $06 INTO $D017,
DLI        = WSYNC, $08 INTO $D017,
DLI        = WSYNC, $0C INTO $D017,
DLI        = WSYNC, $0C INTO $D017,
DLI        = WSYNC, $08 INTO $D017,
DLI        = WSYNC, $06 INTO $D017,
DLI        = WSYNC, $04 INTO $D017

The way you've coded things already automatically injects the WSYNC store between the LDA/STA of the first 'INTO' and so the same principle can apply to an INTO preceded with a WSYNC.

This would permit multiple WSYNC entries to be used, e.g. re-writing the above:

 

DLI SET graded_dli = $04 INTO $D017,
DLI        = WSYNC, $06 INTO $D017,
DLI        = WSYNC, $08 INTO $D017,
DLI        = WSYNC, $0C INTO $D017,
DLI        = WSYNC, WSYNC, $08 INTO $D017,
DLI        = WSYNC, $06 INTO $D017,
DLI        = WSYNC, $04 INTO $D017

resulting in something like:

fb_lbl_graded_dli:
    .byte    72
    .byte    138
    .byte    72
    .byte    169
    .byte    $04
    .byte    141
    .word    WSYNC
    .byte    141
    .word    53271
    .byte    169
    .byte    $06
    .byte    141
    .word    WSYNC
    .byte    141
    .word    53271
    .byte    169
    .byte    $08
    .byte    141
    .word    WSYNC
    .byte    141
    .word    53271
    .byte    169
    .byte    $0C
    .byte    141
    .word    WSYNC
    .byte    141
    .word    53271
    .byte    141
    .word    WSYNC
    .byte    169
    .byte    $08
    .byte    141
    .word    WSYNC
    .byte    141
    .word    53271
    .byte    169
    .byte    $06
    .byte    141
    .word    WSYNC
    .byte    141
    .word    53271
    .byte    169
    .byte    $04
    .byte    141
    .word    WSYNC
    .byte    141
    .word    53271
    .byte    230
    .byte    COLRSH
    .byte    104
    .byte    170
    .byte    104
    .byte    64


[EDIT] So taking the test.bas, building it with 'fb-int' and then replacing the DLI code in the 'asm' file with that given above and then building that with:

cl65 -t atari -C fastbasic.cfg -o test.xex test.asm fastbasic-int.lib

 

gives:

 

image.thumb.png.e9f82779998f4c7e5c82ac197dda8bed.png

test.asm test.bas test.xex

Edited by Wrathchild
Example added
Link to comment
Share on other sites

Hi!

 

First, thank to all for the suggestions.

 

1 hour ago, Wrathchild said:

That makes it quite a restrictive model and a not too-realistic use-case of DLIs, i.e. you'll potentially need to limit the number of changes due to time criticality. 

Yes, it is somewhat restrictive, but I designed it to be as simple as possible, it is BASIC after all :) 

 

1 hour ago, Wrathchild said:

The 'DLi counter' you are exploiting is the COLRSH register ($4F).

Once the attract mode is triggered this is given a value from middle clock register ($13) during the VBI. (e.g. "bx db(COLRSH)>4")
Whilst in most cases just colours or sprite positions are being set and so at most will then to stop being animated and give them different colours, this is dangerous as it means that access to DATA BYTE arrays are potentially out of bounds.

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.

 

1 hour ago, Wrathchild said:

As the DLI vector isn't refresh by the OS during the Vblank (e.g. checked with "bx write=$200") this means that the last DLI is responsible for setting the first DLIs address.

During development this often can cause the effect of 'rolling' when the number of interrupts in the Dlist don't match the number in your chain.

Hence setting the first DLI in the VBlank is a good help towards helping to rectify that.

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.

 

1 hour ago, Wrathchild said:

I've experimented and demonstrate what I mean in the attached modified example.


From this I would say it would be nice if DATA WORD support could be added, e.g.


' Dli addresses
DATA dli_adr() WORD = 0,0,0

' Our DLI writes the position and
' colors to Player 1 and Player 2
DLI SET d3a = pos INTO $D000, pos INTO $D001,
DLI        = $28 INTO $D012, $2E INTO $D013,
DLI        = dli_adr INTO $200

DLI SET d3b = pos INTO $D000, pos INTO $D001,
DLI        = $88 INTO $D012,
DLI        = dli_adr INTO $200

DLI SET d3c = pos INTO $D000, pos INTO $D001,
DLI        = $CE INTO $D013,
DLI        = dli_adr INTO $200

dli_adr(0) = ADR(d3b)
dli_adr(1) = ADR(d3c)
dli_adr(2) = ADR(d3a)

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.

 

1 hour ago, Wrathchild said:

So initially I'd thought that the last line of a dli could be:


DLI = DLI d3b

Adding this it would be possible.

1 hour ago, Wrathchild said:

but this isn't possible and so considering if the above was working then the dli_adr could be eliminated by using:


DLI = d3b INTO $200

but as I understand it the lack of forward reference inhibits writing it that way and so the array is suffices.

IMHO, I prefer the syntax with "DLI", as the "INTO" writes only a byte in all other cases.

 

1 hour ago, Wrathchild said:

Which means that actually it is reusable between different screens such as a title page and game screen by applying the addresses of different DLI definitions.

 

Keep up the good work!

 

altdli.bas 1.25 kB · 2 downloads altdli.xex 1.66 kB · 2 downloads

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.

 

17 hours ago, Wrathchild said:

I think the ideal could be something like this - as if you define the routines in reverse order then you can reference them, and the last does not need to set the first if this is done in the VBI, e.g.


DLI SET dli3 = pos INTO $D000, pos INTO $D001,
DLI        = $CE INTO $D013

DLI SET dli2 = pos INTO $D000, pos INTO $D001,
DLI        = $88 INTO $D012,
DLI        = DLI dli3

DLI SET dli1 = pos INTO $D000, pos INTO $D001,
DLI        = $28 INTO $D012, $2E INTO $D013,
DLI        = DLI dli2

VBI SET vb1 = DLI dli1

...

VBI vb1

As an example, the VBI can then also be used to call a linked RMT player routine

 

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?

 

15 hours ago, Wrathchild said:

I'd forgotten to mention another idea, let the user state WSYNC as in the example you are in Gr.0 and even with a DLI stated each line then you can only update registers every 8 lines.

 


DLI SET graded_dli = $04 INTO $D017,
DLI        = WSYNC, $06 INTO $D017,
DLI        = WSYNC, $08 INTO $D017,
DLI        = WSYNC, $0C INTO $D017,
DLI        = WSYNC, WSYNC, $08 INTO $D017,
DLI        = WSYNC, $06 INTO $D017,
DLI        = WSYNC, $04 INTO $D017

 

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!

 

  • Like 2
Link to comment
Share on other sites

20 hours ago, dmsc said:

One question: ¿should VBI vector the immediate or the deferred VBI?

 

My recommendation would be VVBLKD as out-of-the-box the OS is pointing this ($224) to the Pull Y/X/A and RTI code vectored to by XITVBV.

So you would hook this a call to SETVBV with A=7 and XY with the routine's address.

At the end of the constructed VBI code, you would end with "JMP XITVBI".

Injecting "LDA #0, STA $4F" into the VBI code might be OK to do (although does affect attract mode colours) but an alternative could be to offer a command within the VBI such as "NO_ATTRACT" which performs "LDA #0, STA $4D"?  
Perhaps the $224.w value can be copied to $238.w when setting up (before the SETVBV call) as Mapping states that as spare? This can then be loaded into XY and SETVBV called to restore.

Edited by Wrathchild
VBI restore
  • Like 1
Link to comment
Share on other sites

Hi!

On 3/2/2020 at 9:13 AM, Wrathchild said:

My recommendation would be VVBLKD as out-of-the-box the OS is pointing this ($224) to the Pull Y/X/A and RTI code vectored to by XITVBV.

So you would hook this a call to SETVBV with A=7 and XY with the routine's address.

At the end of the constructed VBI code, you would end with "JMP XITVBI".

Injecting "LDA #0, STA $4F" into the VBI code might be OK to do (although does affect attract mode colours) but an alternative could be to offer a command within the VBI such as "NO_ATTRACT" which performs "LDA #0, STA $4D"?  
Perhaps the $224.w value can be copied to $238.w when setting up (before the SETVBV call) as Mapping states that as spare? This can then be loaded into XY and SETVBV called to restore.

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!

 

  • Like 1
Link to comment
Share on other sites

So...

Wow this is pretty cool!

 

I was actually poking around trying to find out which was faster Turbo Basic or Fast Basic when I came across this.  This is pretty impressive for not having to bust out the assembler!  Alas turbo basic cannot compete with this!

 

After seeing this post I poked around and got the impression you wrote this in CC65?  I used that a few years back and thought it was best used from code structure and then you cleaned up the assembly code to make it fast later?

 

Does CC65 give you decent performance these days without manually patching up the generated code?

 

I was actually wondering if compiled fast basic was faster than CC65 until I read you were using it! :)

 

Later,

Pete

 

 

 

 

 

 

Link to comment
Share on other sites

I like the idea of a special syntax to force some extra WSYNC in a DLI, buy I don't like the use of the "NEXT" word for it, as it has another meaning in BASIC. Keep "WSYNC" or use "WAIT" ("W.").

 

About the remove of the comma after the WSYNC, I'm not sure if it is a good idea, as I would like to do something like this:

DLI SET half = $66 INTO 710, NEXT NEXT NEXT NEXT $84 INTO 710

With the comma, I could say (using "WAIT"):

 

DLI SET half = $66 INTO 710, WAIT, WAIT, WAIT, WAIT, $84 INTO 710

or, better:

DLI SET half = $66 INTO 710, WAIT 4, $84 INTO 710

Can the parser repeat the "emit" based on a constant parameter like in "WAIT 4"?

 

 

About chaining DLIs, I agree the use of another DLI reference:

DLI SET dli1 = pos INTO $D000, pos INTO $D001,
DLI        = $28 INTO $D012, $2E INTO $D013,
DLI        = DLI dli2

It might be strange that DLIs must be defined in the inverse order, but I understand the language (parser) restrictions.

 

Link to comment
Share on other sites

Hi!

2 hours ago, vitoco said:

I like the idea of a special syntax to force some extra WSYNC in a DLI, buy I don't like the use of the "NEXT" word for it, as it has another meaning in BASIC. Keep "WSYNC" or use "WAIT" ("W.").

 

About the remove of the comma after the WSYNC, I'm not sure if it is a good idea, as I would like to do something like this:


DLI SET half = $66 INTO 710, NEXT NEXT NEXT NEXT $84 INTO 710

With the comma, I could say (using "WAIT"):

 


DLI SET half = $66 INTO 710, WAIT, WAIT, WAIT, WAIT, $84 INTO 710

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.

 

2 hours ago, vitoco said:

or, better:


DLI SET half = $66 INTO 710, WAIT 4, $84 INTO 710

Can the parser repeat the "emit" based on a constant parameter like in "WAIT 4"?

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.

 

2 hours ago, vitoco said:

About chaining DLIs, I agree the use of another DLI reference:


DLI SET dli1 = pos INTO $D000, pos INTO $D001,
DLI        = $28 INTO $D012, $2E INTO $D013,
DLI        = DLI dli2

It might be strange that DLIs must be defined in the inverse order, but I understand the language (parser) restrictions.

If you have any suggestions, you can also comment in the github issues, so I don't forget about it.

 

Have Fun!

 

Link to comment
Share on other sites

49 minutes ago, dmsc said:

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!

 

 

You are right, I think I do not realize the problem of "spaces".

 

Is there any difference between:

DLISETx=0INTO710,15INTO711

and:

DLI SET x = 0 INTO 710 , 15 INTO 711

???

 

Of which kind of split are you talking about? 

 

Or for "space" you mean a check for an optional argument just before the comma?

 

49 minutes ago, dmsc said:

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.

I preferred the "comma" version because of "WAIT 4," won't require extra tests if it says "WAIT 4 INTO 710". 

 

I guess that "4 WAIT" will have the same problem in current implementation of the parser.

 

Link to comment
Share on other sites

Hi!

25 minutes ago, vitoco said:

You are right, I think I do not realize the problem of "spaces".

 

Is there any difference between:


DLISETx=0INTO710,15INTO711

and:


DLI SET x = 0 INTO 710 , 15 INTO 711

???

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.

 

25 minutes ago, vitoco said:

Of which kind of split are you talking about? 

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?

 

25 minutes ago, vitoco said:

Or for "space" you mean a check for an optional argument just before the comma?

 

I preferred the "comma" version because of "WAIT 4," won't require extra tests if it says "WAIT 4 INTO 710". 

 

I guess that "4 WAIT" will have the same problem in current implementation of the parser.

Yes, the parser just is not that smart :) 

 

Have Fun!

 

Link to comment
Share on other sites

20 minutes ago, dmsc said:

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.

Yes... I guess ?

 

20 minutes ago, dmsc said:

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?

In your example, you use the token "NO" as an input terminator which is tricky in terms of the parser to get a success. Without such terminator for this input, I don't know how to return true for this input.

 

Anyway, knowing that I'm missing something, I guess that you need 3 rules:

RULE1:
  "DLI" RULE2 RULE3
RULE2:
  "WSYNC"
RULE3:
  "," RULE2 RULE3

Without commas, it should be something like this:

RULE1:
  "DLI" RULE2
RULE2:
  "WSYNC" RULE2

If I was right, you need an extra rule to parse the comma delimiter. But I don't know how many bytes are those for the compiled parser or table.

 

BTW, sorry about this off-topic!

 

Link to comment
Share on other sites

Hi!

3 hours ago, vitoco said:

Yes... I guess ?

 

In your example, you use the token "NO" as an input terminator which is tricky in terms of the parser to get a success. Without such terminator for this input, I don't know how to return true for this input.

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.

 

3 hours ago, vitoco said:

Anyway, knowing that I'm missing something, I guess that you need 3 rules:


RULE1:
  "DLI" RULE2 RULE3
RULE2:
  "WSYNC"
RULE3:
  "," RULE2 RULE3

Yes, you need three rules. You only missed a terminator (as written, your grammar only parses infinite inputs :P ), to terminate before the "," you need to add a "pass" to the RULE3.

 

3 hours ago, vitoco said:

Without commas, it should be something like this:


RULE1:
  "DLI" RULE2
RULE2:
  "WSYNC" RULE2

If I was right, you need an extra rule to parse the comma delimiter. But I don't know how many bytes are those for the compiled parser or table.

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 :) 

 

3 hours ago, vitoco said:

BTW, sorry about this off-topic!

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!

 

  • Like 2
Link to comment
Share on other sites

  • 3 weeks later...

Hi!

16 hours ago, RALPH! said:

Are there ways to include assembler code that compile with the program? That way there not be need use separate assembler and make binary for make into data statements.  Mad Pascal and 7800 Basic have ASM statement.

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!

 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...