Skip to content

Commit 96356f0

Browse files
committed
Add EVALUATE MOVE ERASE and tests.
1 parent 063d394 commit 96356f0

File tree

6 files changed

+169
-13
lines changed

6 files changed

+169
-13
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,8 @@ error if it cannot be cannot be cross compiled.
511511
* `EMIT`
512512
* Deferred so a program can output to any interface.
513513
`EMIT` is used for all printing, such as `.`.
514+
* `EVALUATE`
515+
* Can only run on host.
514516
* `EXECUTE`
515517
* `EXIT`
516518
* `FIND`
@@ -528,6 +530,8 @@ error if it cannot be cannot be cross compiled.
528530
* `MAX`
529531
* `MIN`
530532
* `MOD`
533+
* `MOVE`
534+
* Does not handle cases where the data overlaps.
531535
* `NEGATE`
532536
* `OR`
533537
* `OVER`
@@ -581,6 +585,7 @@ Missing words may be implemented in the future.
581585
* `DEFER@`
582586
* `ENDCASE`
583587
* `ENDOF`
588+
* `ERASE`
584589
* `FALSE`
585590
* `HEX`
586591
* `IS`

pkg/forth/builtin/02_core.f

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,3 +422,25 @@
422422
ALLOT \ change the data space accordingly
423423
;
424424

425+
: ERASE ( addr u -- )
426+
DUP 0> IF
427+
0 DO
428+
( addr )
429+
0 OVER ! \ store 0
430+
1+ ( addr+1 )
431+
LOOP
432+
DROP
433+
ELSE
434+
2DROP
435+
THEN
436+
;
437+
438+
: MOVE ( addr1 addr2 u -- )
439+
0 ?DO
440+
( addr1 addr2 )
441+
OVER @ ( addr1 addr2 val1 )
442+
OVER ! ( addr1 addr2 ) \ store val1
443+
1+ SWAP 1+ SWAP ( addr1+1 addr2+1 )
444+
LOOP
445+
2DROP
446+
;

pkg/forth/parse.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ package forth
1111
type ParseArea struct {
1212
area []byte
1313
index int
14+
15+
savedArea []byte
16+
savedIndex int
1417
}
1518

1619
// Set up the parse area.
@@ -27,6 +30,24 @@ func (p *ParseArea) Fill(bytes []byte) error {
2730
return nil
2831
}
2932

33+
func (p *ParseArea) Save() error {
34+
p.savedArea = p.area
35+
p.savedIndex = p.index
36+
// reset the area
37+
p.area = nil
38+
p.index = 0
39+
return nil
40+
}
41+
42+
func (p *ParseArea) Restore() error {
43+
p.area = p.savedArea
44+
p.index = p.savedIndex
45+
// reset the saved area
46+
p.savedArea = nil
47+
p.savedIndex = 0
48+
return nil
49+
}
50+
3051
func (p *ParseArea) Word(delimiter byte) ([]byte, error) {
3152
// trim starting whitespace
3253
startIndex := p.index

pkg/forth/primitive.go

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func PrimitiveSetup(vm *VirtualMachine) error {
7474
if !ok {
7575
return EntryError(entry, "requires an address cell")
7676
}
77-
name, err := cellsToString(cellAddr.Entry.Word.(*WordForth).Cells)
77+
name, err := countedCellsToString(cellAddr.Entry.Word.(*WordForth).Cells)
7878
if err != nil {
7979
return JoinEntryError(err, entry, "could not parse name")
8080
}
@@ -130,7 +130,7 @@ func PrimitiveSetup(vm *VirtualMachine) error {
130130
if !ok {
131131
return EntryError(entry, "requires an address cell")
132132
}
133-
name, err := cellsToString(cellAddr.Entry.Word.(*WordForth).Cells)
133+
name, err := countedCellsToString(cellAddr.Entry.Word.(*WordForth).Cells)
134134
if err != nil {
135135
return JoinEntryError(err, entry, "could not parse name")
136136
}
@@ -510,7 +510,7 @@ func PrimitiveSetup(vm *VirtualMachine) error {
510510
if !ok {
511511
return EntryError(entry, "requires an address cell")
512512
}
513-
name, err := cellsToString(cellAddr.Entry.Word.(*WordForth).Cells)
513+
name, err := countedCellsToString(cellAddr.Entry.Word.(*WordForth).Cells)
514514
if err != nil {
515515
return JoinEntryError(err, entry, "could not convert input to string")
516516
}
@@ -548,6 +548,45 @@ func PrimitiveSetup(vm *VirtualMachine) error {
548548
return nil
549549
},
550550
},
551+
{
552+
name: "EVALUATE",
553+
goFunc: func(vm *VirtualMachine, entry *DictionaryEntry) error {
554+
// get the size
555+
size, err := vm.Stack.PopNumber()
556+
if err != nil {
557+
return PopError(err, entry)
558+
}
559+
// get the code
560+
cell, err := vm.Stack.Pop()
561+
if err != nil {
562+
return JoinEntryError(err, entry, "could not pop code")
563+
}
564+
cellAddr, ok := cell.(CellAddress)
565+
if !ok {
566+
return EntryError(entry, "requires code")
567+
}
568+
code, err := cellsToString(cellAddr.Entry.Word.(*WordForth).Cells, int(size), cellAddr.UpperByte)
569+
if err != nil {
570+
return JoinEntryError(err, entry, "could not parse code")
571+
}
572+
// save the parse area
573+
err = vm.ParseArea.Save()
574+
if err != nil {
575+
return JoinEntryError(err, entry, "could not save parse area")
576+
}
577+
// execute
578+
err = vm.Execute([]byte(code))
579+
if err != nil {
580+
return JoinEntryError(err, entry, "error while executing: %s", code)
581+
}
582+
// restore parse area
583+
err = vm.ParseArea.Restore()
584+
if err != nil {
585+
return JoinEntryError(err, entry, "could not restore parse area")
586+
}
587+
return nil
588+
},
589+
},
551590
{
552591
name: "EXECUTE",
553592
goFunc: func(vm *VirtualMachine, entry *DictionaryEntry) error {
@@ -610,7 +649,7 @@ func PrimitiveSetup(vm *VirtualMachine) error {
610649
if !ok {
611650
return EntryError(entry, "requires a name")
612651
}
613-
name, err := cellsToString(cellAddr.Entry.Word.(*WordForth).Cells)
652+
name, err := countedCellsToString(cellAddr.Entry.Word.(*WordForth).Cells)
614653
if err != nil {
615654
return JoinEntryError(err, entry, "could not parse name")
616655
}
@@ -1365,6 +1404,12 @@ func PrimitiveSetup(vm *VirtualMachine) error {
13651404
return nil
13661405
}
13671406
return EntryError(entry, "could not subtract %s type %T from %s type %T due to types", right, right, left, left)
1407+
case CellNumber:
1408+
err = vm.Stack.Push(CellAddress{r.Entry, r.Offset - int(l.Number), false})
1409+
if err != nil {
1410+
return PushError(err, entry)
1411+
}
1412+
return nil
13681413
default:
13691414
return EntryError(entry, "could not subtract %s type %T from %s type %T due to types", right, right, left, left)
13701415
}
@@ -2491,7 +2536,7 @@ func parseWord(vm *VirtualMachine, entry *DictionaryEntry) (string, error) {
24912536
if !ok {
24922537
return "", EntryError(entry, "name argument needs to be an address to a string")
24932538
}
2494-
name, err := cellsToString(cellAddr.Entry.Word.(*WordForth).Cells)
2539+
name, err := countedCellsToString(cellAddr.Entry.Word.(*WordForth).Cells)
24952540
if err != nil {
24962541
return "", JoinEntryError(err, entry, "could not parse name")
24972542
}
@@ -2511,7 +2556,7 @@ func parseAssembly(vm *VirtualMachine, entry *DictionaryEntry) ([]string, error)
25112556
}
25122557
switch c := cell.(type) {
25132558
case CellAddress:
2514-
substr, err := cellsToString(c.Entry.Word.(*WordForth).Cells)
2559+
substr, err := countedCellsToString(c.Entry.Word.(*WordForth).Cells)
25152560
if err != nil {
25162561
return nil, JoinEntryError(err, entry, "could not convert input to string")
25172562
}

pkg/forth/suite_test.go

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -454,8 +454,25 @@ func TestSuite(t *testing.T) {
454454
// ENDCASE does not have any tests
455455
// ENDOF does not have any tests
456456
// ENVIRONMENT?
457-
// ERASE
458-
// EVALUATE
457+
// ERASE does not have any tests
458+
{
459+
name: "EVALUATE",
460+
setup: `
461+
: GE1 S" 123" ; IMMEDIATE
462+
: GE2 S" 123 1+" ; IMMEDIATE
463+
: GE3 S" : GE4 345 ;" ;
464+
: GE5 EVALUATE ; IMMEDIATE
465+
T{ GE1 EVALUATE -> 123 }T \ TEST EVALUATE IN INTERPRET STATE
466+
T{ GE2 EVALUATE -> 124 }T
467+
T{ GE3 EVALUATE -> }T
468+
T{ GE4 -> 345 }T
469+
470+
T{ : GE6 GE1 GE5 ; -> }T \ TEST EVALUATE IN COMPILE STATE
471+
T{ GE6 -> 123 }T
472+
T{ : GE7 GE2 GE5 ; -> }T
473+
T{ GE7 -> 124 }T
474+
`,
475+
},
459476
// EXECUTE does not have any tests
460477
// EXIT does not have any tests
461478
{
@@ -660,7 +677,38 @@ func TestSuite(t *testing.T) {
660677
`,
661678
},
662679
// MOD
663-
// MOVE
680+
{
681+
name: "MOVE",
682+
setup: `
683+
T{ 20 BUFFER: FBUF -> }T
684+
T{ 20 FBUF ! 20 FBUF 1+ ! 20 FBUF 2 + ! -> }T
685+
T{ : SEEBUF FBUF @ FBUF 1+ @ FBUF 2 + @ ; -> }T
686+
T{ 20 BUFFER: SBUF -> }T
687+
T{ 12 SBUF ! 34 SBUF 1+ ! 56 SBUF 2 + ! -> }T
688+
`,
689+
code: `
690+
// The actual test suite is wrong for forths in which
691+
// the address units are greater than the character size.
692+
// This test is modified to work with that.
693+
T{ FBUF FBUF 3 CHARS MOVE -> }T \ BIZARRE SPECIAL CASE
694+
T{ SEEBUF -> 20 20 20 }T
695+
T{ SBUF FBUF 0 CHARS MOVE -> }T
696+
T{ SEEBUF -> 20 20 20 }T
697+
698+
T{ SBUF FBUF 1 CHARS MOVE -> }T
699+
T{ SEEBUF -> 12 20 20 }T
700+
701+
T{ SBUF FBUF 3 MOVE -> }T
702+
T{ SEEBUF -> 12 34 56 }T
703+
704+
// TODO fix the overlapping case
705+
// T{ FBUF FBUF 1+ 2 MOVE -> }T
706+
// T{ SEEBUF -> 12 12 34 }T
707+
708+
// T{ FBUF CHAR+ FBUF 2 CHARS MOVE -> }T
709+
// T{ SEEBUF -> 12 34 34 }T
710+
`,
711+
},
664712
// M*
665713
{
666714
name: "-",

pkg/forth/utils.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,29 @@ func cellsToBytes(cells []Cell) ([]byte, error) {
5151
return out, nil
5252
}
5353

54-
func cellsToString(cells []Cell) (string, error) {
54+
func countedCellsToString(cells []Cell) (string, error) {
5555
bytes, err := cellsToBytes(cells)
5656
if err != nil {
5757
return "", err
5858
}
5959
length := bytes[0]
60-
if length < 0 || int(length+1) > len(bytes) {
61-
return "", fmt.Errorf("counted string length is invalid: %d", length)
60+
return bytesToString(bytes[1:], int(length))
61+
}
62+
63+
func cellsToString(cells []Cell, length int, upper bool) (string, error) {
64+
bytes, err := cellsToBytes(cells)
65+
if err != nil {
66+
return "", err
67+
}
68+
if upper {
69+
return bytesToString(bytes[1:], length)
70+
}
71+
return bytesToString(bytes, length)
72+
}
73+
74+
func bytesToString(bytes []byte, length int) (string, error) {
75+
if length < 0 || length > len(bytes) {
76+
return "", fmt.Errorf("string length is invalid: %d", length)
6277
}
63-
return string(bytes[1 : length+1]), nil
78+
return string(bytes[:length]), nil
6479
}

0 commit comments

Comments
 (0)