Resolve "add forth task"
This commit is contained in:
parent
f70c329549
commit
69288fe91e
4 changed files with 440 additions and 0 deletions
104
forth/README.md
Normal file
104
forth/README.md
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
## forth
|
||||||
|
|
||||||
|
В этой задаче нужно написать интерпретатор [языка программирования Forth](https://en.wikipedia.org/wiki/Forth_(programming_language)).
|
||||||
|
|
||||||
|
Forth - это стек-ориентированный, конкатенативный (конкатенация двух фрагментов кода выражает их композицию) язык программирования.
|
||||||
|
|
||||||
|
В нашем примитивном случае язык состоит из стека, хранящего целые числа, и словаря известных операций.
|
||||||
|
Каждая операция вынимает некоторое (возможно нулевое) количество чисел со стека и кладёт некоторое (возможно нулевое) количество чисел на стек.
|
||||||
|
|
||||||
|
Полноценный Forth, с ячейками памяти, циклами, условными переходами и множеством стандартных операций
|
||||||
|
позволяет писать довольно сложные программы и используется в микропроцессорах.
|
||||||
|
|
||||||
|
#### Примеры программ
|
||||||
|
|
||||||
|
В каждом примере на последней строке приведено результирующее значение стека.
|
||||||
|
|
||||||
|
Положить 3 числа на стек:
|
||||||
|
```
|
||||||
|
1 2 3
|
||||||
|
Stack: 1, 2, 3
|
||||||
|
```
|
||||||
|
|
||||||
|
Положить 2 числа на стек, исполнить операцию `+` (достать 2 числа со стека и положить на стек их сумму)
|
||||||
|
```
|
||||||
|
1 2 +
|
||||||
|
Stack: 3
|
||||||
|
```
|
||||||
|
|
||||||
|
Положить число на стек и выполнить операцию `drop` (достать верхнее значение из стека)
|
||||||
|
```
|
||||||
|
1 drop
|
||||||
|
Stack:
|
||||||
|
```
|
||||||
|
|
||||||
|
Определить операцию ("слово" в терминологии Forth) `drop2` для удаления двух верхних значений со стека,
|
||||||
|
положить 2 числа на стек и воспользоваться `drop2`:
|
||||||
|
```
|
||||||
|
: drop2 drop drop ;
|
||||||
|
1 2 drop2
|
||||||
|
Stack:
|
||||||
|
```
|
||||||
|
|
||||||
|
### Грамматика
|
||||||
|
|
||||||
|
У языка нет официальной грамматики.
|
||||||
|
Она определяется простым алгоритмом.
|
||||||
|
Интерпретатор считывает строку ввода,
|
||||||
|
разбивает на части, используя пробелы в качестве разделителя,
|
||||||
|
анализирует каждую часть на принадлежность к лексемам языка ("словам").
|
||||||
|
|
||||||
|
Все известные языку слова хранятся в словаре.
|
||||||
|
Если алгоритм натыкается на знакомое слово, он исполняет связанный с ним исходный код.
|
||||||
|
Незнакомые слова интерпретируются как числа и добавляются в стек.
|
||||||
|
|
||||||
|
В случае успеха интерпретатор продолжает разбор входного потока.
|
||||||
|
Если и поиск, и преобразование чисел завершаются неудачно,
|
||||||
|
интерпретатор печатает слово, за которым следует сообщение об ошибке, указывающее, что слово не распознано,
|
||||||
|
сбрасывает входной поток и ждет нового пользовательского ввода.
|
||||||
|
|
||||||
|
### Что нужно сделать?
|
||||||
|
|
||||||
|
Нужно написать реализацию функций `NewEvaluator` и `Evaluator.Process`.
|
||||||
|
Первая создаёт объект исполнителя Forth кода, а вторая парсит и исполняет.
|
||||||
|
|
||||||
|
Требуется поддержать следующие слова:
|
||||||
|
* `+`, `-`, `*`, `/` удаляют со стека два верхних значения и кладут результат соответствующей целочисленной арифметической операции
|
||||||
|
* `dup` кладёт на стек верхнее значение
|
||||||
|
* `over` кладёт на стек второе сверху значение
|
||||||
|
* `drop` удаляет верхнее значение со стека
|
||||||
|
* `swap` меняет два верхних значения стека местами
|
||||||
|
|
||||||
|
При нехватке аргументов `Process` должна возвращать ошибки.
|
||||||
|
|
||||||
|
Также нужно поддержать определение новых слов.
|
||||||
|
```
|
||||||
|
: word definition ;
|
||||||
|
```
|
||||||
|
где `word` -- это case-insensitive имя новой операции,
|
||||||
|
а `defenition` состоит из известных слов и чисел, разделённых пробелами.
|
||||||
|
Слова можно переопределять.
|
||||||
|
|
||||||
|
### Проверка решения
|
||||||
|
|
||||||
|
Для запуска тестов нужно выполнить следующую команду:
|
||||||
|
```
|
||||||
|
go test -v ./forth/...
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Интерактивная среда
|
||||||
|
|
||||||
|
В [main.go](./main.go) написана небольшая обёртка вашей реализации,
|
||||||
|
позволяющая интерактивно взаимодействовать с интерпретатором.
|
||||||
|
```
|
||||||
|
go build . && ./forth
|
||||||
|
Welcome to Forth evaluator! To exit type "bye".
|
||||||
|
>1 2 +
|
||||||
|
Stack: 3
|
||||||
|
>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ссылки
|
||||||
|
|
||||||
|
* https://en.wikipedia.org/wiki/Forth_(programming_language)
|
||||||
|
* https://golang.org/pkg/strings/
|
18
forth/eval.go
Normal file
18
forth/eval.go
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// +build !solution
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
type Evaluator struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEvaluator creates evaluator.
|
||||||
|
func NewEvaluator() *Evaluator {
|
||||||
|
return &Evaluator{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process evaluates sequence of words or definition.
|
||||||
|
//
|
||||||
|
// Returns resulting stack state and an error.
|
||||||
|
func (e *Evaluator) Process(row string) ([]int, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
274
forth/eval_test.go
Normal file
274
forth/eval_test.go
Normal file
|
@ -0,0 +1,274 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testCase struct {
|
||||||
|
description string
|
||||||
|
input []string
|
||||||
|
expected []int
|
||||||
|
error bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var testCases = []testCase{
|
||||||
|
{
|
||||||
|
description: "push numbers",
|
||||||
|
input: []string{"1 2 3 4 5"},
|
||||||
|
expected: []int{1, 2, 3, 4, 5},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "add",
|
||||||
|
input: []string{"1 2 +"},
|
||||||
|
expected: []int{3},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "nothing to add",
|
||||||
|
input: []string{"+"},
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "add arity",
|
||||||
|
input: []string{"1 +"},
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "sub",
|
||||||
|
input: []string{"3 4 -"},
|
||||||
|
expected: []int{-1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "nothing to sub",
|
||||||
|
input: []string{"-"},
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "sub arity",
|
||||||
|
input: []string{"1 -"},
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "mul",
|
||||||
|
input: []string{"2 4 *"},
|
||||||
|
expected: []int{8},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "nothing to mul",
|
||||||
|
input: []string{"*"},
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "mul arity",
|
||||||
|
input: []string{"1 *"},
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "div",
|
||||||
|
input: []string{"12 3 /"},
|
||||||
|
expected: []int{4},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "integer division",
|
||||||
|
input: []string{"8 3 /"},
|
||||||
|
expected: []int{2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "division by zero",
|
||||||
|
input: []string{"4 0 /"},
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "nothing to div",
|
||||||
|
input: []string{"/"},
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "div arity",
|
||||||
|
input: []string{"1 /"},
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "add sub",
|
||||||
|
input: []string{"1 2 + 4 -"},
|
||||||
|
expected: []int{-1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "mul div",
|
||||||
|
input: []string{"2 4 * 3 /"},
|
||||||
|
expected: []int{2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "dup",
|
||||||
|
input: []string{"1 dup"},
|
||||||
|
expected: []int{1, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "dup top",
|
||||||
|
input: []string{"1 2 dup"},
|
||||||
|
expected: []int{1, 2, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "nothing to dup",
|
||||||
|
input: []string{"dup"},
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "drop",
|
||||||
|
input: []string{"1 drop"},
|
||||||
|
expected: []int{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "drop top",
|
||||||
|
input: []string{"1 2 drop"},
|
||||||
|
expected: []int{1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "nothing to drop",
|
||||||
|
input: []string{"drop"},
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "swap",
|
||||||
|
input: []string{"1 2 swap"},
|
||||||
|
expected: []int{2, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "swap top",
|
||||||
|
input: []string{"1 2 3 swap"},
|
||||||
|
expected: []int{1, 3, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "nothing to swap",
|
||||||
|
input: []string{"swap"},
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "swap arity",
|
||||||
|
input: []string{"1 swap"},
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "over",
|
||||||
|
input: []string{"1 2 over"},
|
||||||
|
expected: []int{1, 2, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "over2",
|
||||||
|
input: []string{"1 2 3 over"},
|
||||||
|
expected: []int{1, 2, 3, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "nothing to over",
|
||||||
|
input: []string{"over"},
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "over arity",
|
||||||
|
input: []string{"1 over"},
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "user-defined",
|
||||||
|
input: []string{": dup-twice dup dup ;", "1 dup-twice"},
|
||||||
|
expected: []int{1, 1, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "user-defined order",
|
||||||
|
input: []string{": countup 1 2 3 ;", "countup"},
|
||||||
|
expected: []int{1, 2, 3},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "user-defined override",
|
||||||
|
input: []string{": foo dup ;", ": foo dup dup ;", "1 foo"},
|
||||||
|
expected: []int{1, 1, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "built-in override",
|
||||||
|
input: []string{": swap dup ;", "1 swap"},
|
||||||
|
expected: []int{1, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "built-in operator override",
|
||||||
|
input: []string{": + * ;", "3 4 +"},
|
||||||
|
expected: []int{12},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "no redefinition",
|
||||||
|
input: []string{": foo 5 ;", ": bar foo ;", ": foo 6 ;", "bar foo"},
|
||||||
|
expected: []int{5, 6},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "reuse in definition",
|
||||||
|
input: []string{": foo 10 ;", ": foo foo 1 + ;", "foo"},
|
||||||
|
expected: []int{11},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "redefine numbers",
|
||||||
|
input: []string{": 1 2 ;"},
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "non-existent word",
|
||||||
|
input: []string{"foo"},
|
||||||
|
error: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "DUP case insensitivity",
|
||||||
|
input: []string{"1 DUP Dup dup"},
|
||||||
|
expected: []int{1, 1, 1, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "DROP case insensitivity",
|
||||||
|
input: []string{"1 2 3 4 DROP Drop drop"},
|
||||||
|
expected: []int{1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "SWAP case insensitivity",
|
||||||
|
input: []string{"1 2 SWAP 3 Swap 4 swap"},
|
||||||
|
expected: []int{2, 3, 4, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "OVER case insensitivity",
|
||||||
|
input: []string{"1 2 OVER Over over"},
|
||||||
|
expected: []int{1, 2, 1, 2, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "user-defined case insensitivity",
|
||||||
|
input: []string{": foo dup ;", "1 FOO Foo foo"},
|
||||||
|
expected: []int{1, 1, 1, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "definition case insensitivity",
|
||||||
|
input: []string{": SWAP DUP Dup dup ;", "1 swap"},
|
||||||
|
expected: []int{1, 1, 1, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEval(t *testing.T) {
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
stack, err := eval(tc.input)
|
||||||
|
if tc.error {
|
||||||
|
require.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tc.expected, stack)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func eval(input []string) ([]int, error) {
|
||||||
|
e := NewEvaluator()
|
||||||
|
var stack []int
|
||||||
|
for _, row := range input {
|
||||||
|
var err error
|
||||||
|
stack, err = e.Process(row)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stack, nil
|
||||||
|
}
|
44
forth/main.go
Normal file
44
forth/main.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// +build !change
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const exitCommand = "bye"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
e := NewEvaluator()
|
||||||
|
|
||||||
|
fmt.Printf("Welcome to Forth evaluator! To exit type %q.\n", exitCommand)
|
||||||
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
|
for {
|
||||||
|
fmt.Print(">")
|
||||||
|
scanner.Scan()
|
||||||
|
|
||||||
|
text := scanner.Text()
|
||||||
|
if text == exitCommand {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
stack, err := e.Process(text)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Evaluation error: %s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
printStack(stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printStack(stack []int) {
|
||||||
|
s := make([]string, 0, len(stack))
|
||||||
|
for _, n := range stack {
|
||||||
|
s = append(s, strconv.Itoa(n))
|
||||||
|
}
|
||||||
|
fmt.Printf("Stack: %s\n", strings.Join(s, ", "))
|
||||||
|
}
|
Loading…
Reference in a new issue