Jump to content

Recommended Posts

I've been working on a simple text editor for some time, and uploaded it to Github today.

The editor is¬†heavily inspired by GNU Nano, but not intended to have all its functions. It's just too much work in 65c02 assembly, at least for me¬†ūüôā

At this moment basic editing works more or less, and the program is fairly stable.

Text entered by the user is stored in banked RAM.

File handling waits until the emulator supports reading and writing sequential files.

Check it out on github

https://github.com/stefan-b-jakobsson/x16-edit

I also include the binary for my 0.0.1 release. It's tested in emulator version r37.

x16edit-0.0.1.prg

  • Like 9

Share this post


Link to post
Share on other sites
Posted (edited)

A couple of things I've been thinking about.

  • How can you reliably determine if the CTRL key is pressed? For now the I'm¬†reading the keyboard with the KERNAL routine GETIN. It mostly works fine, but there are some overlaps. For instance, CTRL+s is the same as HOME.
  • Is there any way to determine the current character mode (PETSCII OR ISO)?
  • And, is it somehow possible to get the screen width (in columns)?
Edited by Stefan

Share this post


Link to post
Share on other sites
7 hours ago, Stefan said:

A couple of things I've been thinking about.

  • How can you reliably determine if the CTRL key is pressed? For now the I'm¬†reading the keyboard with the KERNAL routine GETIN. It mostly works fine, but there are some overlaps. For instance, CTRL+s is the same as HOME.
  • Is there any way to determine the current character mode (PETSCII OR ISO)?
  • And, is it somehow possible to get the screen width (in columns)?

If you don't have anything plugged into the joystick 1 port, you can use the joystick driver to look for the A button, which will map to Ctrl.

If you inspect VRAM, you should be able to check the character map, looking for one of the characters that are different.

That's a simple VERA setting. DC_HSCALE is 128 for 80 column mode, and 64 for 40 column

  • Like 1

Share this post


Link to post
Share on other sites
Posted (edited)

This is very clever, thanks!   Hmmm and it might be useful for creating banked data....

Would it make sense to use the function keys in addition to control-keys?   Do you always save starting at bank 1, or is the page command used to flip through banks?

Edited by rje

Share this post


Link to post
Share on other sites

@SlithyMatt Thank you for your tips. I will try them out.

@rje Good idea to use function keys as alternatives.

The program uses all of banked RAM, for now starting at bank 1 page $a0. Bank 0 may be used by the KERNAL if I understand the Programmer's Reference Guide correctly (emulator r37).

Text entered by the user is, however, not organized as one continuous string. Instead each memory page is dealt with as a separate string. The memory pages are dynamically linked to each other. To make this possible each memory page also contains some metadata. Consequently, the program isn't suitable for directly creating raw data in banked RAM. For clarity, when I'm talking about memory pages, I mean for instance the address interval $a000-$a0ff or $bf00-$bfff, i.e. 256 bytes.

I chose this memory model in order to be able to handle large texts. Otherwise you could end up in a situation where the text is 1 MB and the user inserts new text at the top. If the text was stored as a continuous string, such an operation would demand that the program copies/moves 1 MB of data one step forward in the memory before inserting the new text. I think that would be too slow.

Every memory page used by the program contains the following fields:

Offset   Content

00          Previous memory page / Bank number

01          Previous memory page / AddressH

02          Next memory page / Bank number

03          Next memory page / AddressH

04          Length of text stored in this memory page (0-251)

05-ff      Text data (max 251 bytes)

It is a bit hard to describe the memory model in a short and easy way. You may run the program and hit restore. Inspect the memory usage with the built-in monitor.

In the monitor you need to type the letter O01 to select RAM bank 1.

The aim of the program is to store pure text files in the filesystem. It would be quite easy to write a tool that loads data from a file into banked RAM in the way I think you would like to do.

  • Thanks 1

Share this post


Link to post
Share on other sites
Posted (edited)

I think I understand. ¬†You‚Äôve implemented a doubly-linked list of dynamic strings. ¬†Instead of ‚Äúpure‚ÄĚ memory addressing, you use bank number.

Do you need an extra byte for address low?  Ah maybe you assume 256 byte blocks so that you have an easier time with managing the memory!   On overflow, you allocate and insert a new block.  Got it!

You‚Äôre¬†using a linked list, not unlike Commodore disk blocks... which are also 256 bytes. ¬†With them, the first two bytes form the pointer to the next block, so they‚Äôre only singly-linked, whereas¬†yours are doubly linked. ¬†The final block in a disk file holds the final block‚Äôs character count and a zero. ¬†Your design is more flexible, in that each ‚Äúblock‚ÄĚ (your ‚Äúpage‚ÄĚ)¬†is essentially a dynamic string with its own length.

 

Hmmm,¬†your addressing is like¬†a virtual ‚Äúdiskette image‚ÄĚ of up to 256¬†‚Äútracks‚ÄĚ of 32 ‚Äúblocks‚ÄĚ... with track zero off limits, used as end of file or similar...¬† ¬†¬† or if you looked at it sideways, 32 tracks of 256¬†blocks...

Edited by rje
  • Like 1

Share this post


Link to post
Share on other sites

You got it! And was able to describe it more clearly than me I think.

Actually the 1541 disk format was my initial inspiration.

Share this post


Link to post
Share on other sites
3 hours ago, Stefan said:

You got it! And was able to describe it more clearly than me I think.

Actually the 1541 disk format was my initial inspiration.

That is SO COOL. ¬†So do you have a BAM? ¬† Or maybe just a ‚Äúfree list‚ÄĚ?

  • Like 1

Share this post


Link to post
Share on other sites

This is awesome. If you want you can use the code I published here to implement a syntax highlighter in your editor ūüôā

I don't mean to just copy paste everything as is but at least take a look at the parser and even copy whatever you need. ^^

  • Like 1

Share this post


Link to post
Share on other sites
29 minutes ago, rje said:

That is SO COOL. ¬†So do you have a BAM? ¬† Or maybe just a ‚Äúfree list‚ÄĚ?

There's a BAM of sorts. In the code it's called mem_map. It's situated in normal RAM at the end of the code.

Banked RAM has 32 x 256 = 8,192 pages. The map has one bit per page, making it 1,024 bytes. 

Share this post


Link to post
Share on other sites
32 minutes ago, VincentF said:

This is awesome. If you want you can use the code I published here to implement a syntax highlighter in your editor ūüôā

I don't mean to just copy paste everything as is but at least take a look at the parser and even copy whatever you need. ^^

Syntax highlighting would be nice.

I'm worried about performance though.

Multi-line highlighting would be especially hard to implement efficiently. For example multi-line comments in C. Highlighting not dependent on what's happening in the text above or below the current line would be simpler.

However, it's a good idea, and I will have it in mind.

A question: If the program should support highlighting, is there any reasonable way to make "plugins" for different file formats?

I guess the program could load the selected highlighting code into RAM from the filesystem after startup. There could be a standardized jump table at the beginning of the code that exposes vectors to the needed subroutines.

Or is there better way?

Share this post


Link to post
Share on other sites

Pretty sure I've seen syntax highlighting here before, although it worked for the inline editor. I can imagine that could work.

Share this post


Link to post
Share on other sites

According to PRG, screen dimensions are the SCREEN call, $FFED from the C64 KERNAL. X has the number of volume, Y the number of rows.

Share this post


Link to post
Share on other sites
Posted (edited)

I've published my second pre-release (v 0.0.2) of X16 Edit in the downloads section of this web page.

Courtesy of emulator r38 (nice work!), I've implemented basic file handling.

It is now possible to load and save text files to an attached sdcard. Use the -sdcard switch when launching the emulator.

File handling will not work without an attached sdcard. This is a limitation of the emulator, as far as I can tell.

Edited by Stefan

Share this post


Link to post
Share on other sites
On 8/23/2020 at 10:21 PM, Stefan said:


 

.. Every memory page used by the program contains the following fields:

Offset   Content

00 01          Previous memory page / AddressH

02          Next memory page / Bank number

03          Next memory page / AddressH

04          Length of text stored in this memory page (0-251)

05-ff      Text data (max 251 bytes)

For a computer with an 80 column text mode, it might not be necessary to use a whole page. Since the high bit of every page in the High RAM window is always set, with a single byte you can get an address of every half page. Instead of (made up routine name)  ..with the page address in A:

LINEREF: STA LINE+1 : STZ LINE ..

You can:

LINEREF: STZ LINE : SEC : ROR : STA LINE+1 : ROR LINE ...

... so the top seven bits carry the bottom seven hits of the actual address, the top bit is always set to one, and the bottom bit decides if the bottom byte is $00 or $80. You still have text max of 123 chars/line.

And since bits #6 and #7 (of 0-7) as stored are always zero, you can use them to carry special information, like %7=1 is previous line when there is no previous line, next line when there is no next line (easily handles with BMI) and #6=1 means a formatting string instead of text to be printed / displayed:

NEXTLINE: LDY #2 : LDA (LINE),Y : TAX : INY : LDA (LINE),Y : BIT #%11000000 :  BMI ++ : STZ FORMAT : BEQ + : DEC FORMAT

+ STX RAMBANK : STZ LINE : AND #3F : SEC : ROR : STA LINE +1 : ROR LINE : RTS ; Note, carry clear

++ SEC : RTS ; carry set if stalled because already at extreme end

PRIORLINE: LDY #0 : BRA NEXTLINE+2

 

NOTE: The "special" line doesn't have to be a format line, it could be a tag line, so a tag search can zip through and only search for matches to test strings in the tag lines. That also makes it easy to go through and make an topic index by section by zipping through and making a linked list of pointers and line numbers for each tag and then processing that list.

Edited by BruceMcF

Share this post


Link to post
Share on other sites

That's an interesting subject.

The editor might not work as you think. It is essentially a model-view design.

The memory module (=model) has no concept of screen or line. It is just text data. So there is no limit to the length of a line. If you like, the text can be one line, 2MB long. And there are no links to previous or next line or to the previous or next screen page (or any other element of the view module).

When designing this, I calculated the memory usage. 16 bits are needed to represent the bank of the previous and the next memory page. 10 bits are needed to represent the previous and the next memory page high byte (range a0-bf is 1f wide = 5 bits). That is a total of 26 bits, not quite fitting in three bytes. I can't do any better, I think.

The memory not used is 6 bits per memory page. 8,192 memory pages x 6 bits = 6,144 bytes lost of the total 2 MB.

In the future I might come up with a use for those unused bits...

Share this post


Link to post
Share on other sites
16 hours ago, Stefan said:

That's an interesting subject.

The editor might not work as you think. It is essentially a model-view design. ...

When designing this, I calculated the memory usage. 16 bits are needed to represent the bank of the previous and the next memory page. ...

Aha, this is "prior window" and "next window" into the text buffer, with each window containing its own linking and text length information.  Yes, the only relatively full featured editor I've ever written was a line buffer editor, so I fell into habitual assumptions.

So to do an insert, you can look if the next window has enough space to store the text after the insert. If it does, slide its text up, copy the text in the next window into the space, and continue with the insert. If it doesn't, grab a new "empty" window and link it in, and copy the text after the insert point into the new window.

You can go through and do small local memory collect at regular intervals, like when moving multiple windows up the chain, go back to the starting point and see how many windows is required to hold the first four buffers, and if its less than four, go through and compact them, releasing buffers from the "used" linked list and putting them in the "empty" linked list.

Anytime on the 6502 that you go beyond a page, both complexity and size of code expands. With a page sized window, keep the address of the window we are at, CURRENT, in zero page and everything can be done with (zp),Y operations. For windows into a buffer stuffed with text, with each window containing two way linked list pointers and number of text characters, page size windows are ideal, the largest size buffer that allows all copies to be simple loops like:
LDA SBNK: CMP DBNK: BNE +
- LDA (SRC),Y : STA (DEST),Y, : DEY : BNE -
-- LDA SBNK :+ STA BANK : LDA (SRC),Y : LDX DBNK : STX BANK : STA (DEST),Y : DEY : BNE --

One idea that reduces overhead by two bytes per page is have 64K buffers, in 8 consecutive High RAM segments. Then you keep a byte somewhere that has the bank of the first segment, you can use the high three bits of the LINK address to be the offset from the base. Then the link to page / bank converter, passing logical address in A and returning bank in A and page in X, is:

LNK2PG: PHA : AND #%00011111 : ORA #$A0 : TAX : PLA : LSR : LSR : LSR : LSR : CLC : ADC BUFF0 : RTS

There is an advantage to using an offset from a base bank even if you DON'T limit yourself to 64K files, because then you can have multiple BUFFERS, and the same operations can be directed to distinct buffers by just putting a different byte into BUFF0. But if you don't compact it, then "Link To Page" with the logical bank in A and logical page in X is just:

LNK2PG: PHA : TXA : AND #$1F : ORA #$A0 : ORA #$A0 TAX : PLA CLC : ADC BUFF0 : RTS

Even if you don't have an allocated use for the three redundant high bits in the logical page address, there's no harm in building in the ability to use them for some purpose later. For example, you might use them to indicate what KIND of text ... PETSCII, ASCII Latin-1, UTF-8 encoding of Unicode, etc. Having that encoded in logical address of each window makes it less likely that type of text information will be lost, and it makes sense to keep different types of text in different pages, even if there is free space available to merge them.

_____________________________________________________________

Another way to reduce to three bytes overhead per page-window, while retaining the two byte logical address, is with an XOR double-linked list.

In an XOR linked list, the "LINK ADDRESS" is the XOR of the LOGICAL ADDRESS OF "NEXT" and "PRIOR". I am going to assume for simplicity that the logical address is just the high byte of the page address with an assumed low byte of 0. If the above is desired, it still can be, with JSR LNK2PG at strategic intervals, but I'll set that aside.

We store the ACTUAL ADDRESSES of PRIOR, CURRENT, and NEXT in memory in zero page vectors, since this lets us do "LDA (PRIOR),Y : STA ( CURRENT),Y" type operations with less setup. We also have PRBANK, CRBANK, NXBANK.

To move UP the chain, we copy CURRENT to PRIOR and NEXT to CURRENT. And then for the physical address and the bank number of the new NEXT, we XOR the LINK in CURRENT with the ADDRESS stored in zero page. Note that you only access data in the CURRENT window, so whether the prior or next link is in the current bank doesn't matter.

NEXTWIN: LDA CRBANK : STA PRBANK : LDA NXBANK : STA CRBANK : STA BANK
L1: LDA PRBANK : EOR (CURRENT) : STA NXBANK
L2: LDA CURRENT+1 : STA PRIOR+1 : LDA NEXT+1 : STA CURRENT+1 : STZ PRIOR : STZ CURRENT
L3: LDY #1 : LDA PRIOR+1 : EOR (CURRENT),Y : STA NEXT+1 : STZ NEXT : RTS

To move down the chain, we do the equivalent, swapping the role of PRIOR and NEXT.

But to do local access to the current window, we don't need to worry about this. And to do local operations TO the next and prior windows, without moving our current spot in the chain, you still don't

_____________________________________________________________

One reason I was writing a line oriented editor is that a line oriented system is very convenient for FORTH. The base address and number of characters can be handed directly to the interpreter, without any need to copy the lines.

The idea this your approach inspires in me is to do something similar with 80 character line buffers, with the lower 5 bits of the logical page address being the page offset into the $A000 High RAM window, and the high three bits of the page address can be two bits to say which buffer INSIDE the page, 1, 2 or 3, and another bit is free for something else ... perhaps a dirty bit that is set when the line has been modified since the last save.

BANK is 1 byte, the logical Window address is 1byte, with XOR linking only the two of them are needed, and the buffer plus the number of characters in the buffer is 81 bytes, so 83 bytes per window. AND ... 256/3=83.33333. For saving the file, the bank might by converted into the sequence of the bank that has been saved so far, so when loaded the banks are all  relative to the start of the buffer, and it is easy to walk through the banks and convert them to their actual bank locations.

  • Like 1

Share this post


Link to post
Share on other sites

@BruceMcF 

I don't remember ever hearing about XOR linked lists.

You couldn't easily come up with such a concept. I guess it's true that there's creativity in limitation.

The downside is, of coarse, more complex code which might be a bit harder to debug.

Share this post


Link to post
Share on other sites
7 hours ago, Stefan said:

@BruceMcF 

I don't remember ever hearing about XOR linked lists.

You couldn't easily come up with such a concept. I guess it's true that there's creativity in limitation.

The downside is, of coarse, more complex code which might be a bit harder to debug.

I don't know who came up with them, I heard about them when following the comp.lang.forth newsgroup in the late 90s, early 2000's.

There's no particular urgent reason to do either change for the "raw text buffer" approach. For 3 80 character line buffers per page, 5 bytes overhead is two bytes too many, but for one raw text buffer, there's no particular amount of space it has to have, and it would only be 1.2% of the space saved even if both space savings are applied.

Actually, as a rule of thumb, don't rewrite software to reduce an overhead by 1.2% unless you need overhead 1% lower to make it work at all! So its probably not a change I would make.

I would be strongly tempted to make the bank references relative to a stored base bank but I would probably leave them as bytes so any given buffer is free to be as big as desired.

___________________________________________________

So the rest of this is just kibbitzing.

Where the XOR linked list is really handy is when you have things set up for a single linked list, but then you want to add the ability to slide up and down in both directions without changing all of the code that assumes the link is a single address in size.

Transitioning to using them is probably a two step process ... finding code that slides up or down the linked lists and turning all of them into subroutine calls to the same two subroutines, and only then switching the window page creation code over. That's also an approach I picked up from the same Usenet News newsgroup, in Forth it's called factoring.

Edited by BruceMcF
  • Like 1

Share this post


Link to post
Share on other sites

I just published a new version of X16 Edit to the downloads page.

What's new?

  • A lot of updates to the UI
  • Some bug fixes

It might soon even be usable.

  • Like 1

Share this post


Link to post
Share on other sites

Fingers crossed, xForth is not going to be limited to BLOCK files, it WILL be able to INCLUDE text files.

When I was editing gforth script files on the train on the way home from work with my floppy disk Linux back in the late 90's, nano was my go to file editor. My IDE was literally nano and gforth and the command line.

Share this post


Link to post
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.

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.


√ó
√ó
  • Create New...

Important Information

Please review our Terms of Use