Merge branch '23-add-forth-task' into 'master'
Resolve "add forth task" Closes #23 See merge request slon/shad-go-private!36
This commit is contained in:
commit
9e4f34caeb
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