суббота, 13 марта 2021 г.

Продолжаю размышлять о синтаксисе

 Я понял, в чём моя ошибка с массивами типа [] (ValueArray) - я "прячу" данные. Когда пользователь создаёт массив, я создаю новый фрейм на стеке, и данные доступны. Но затем я помещаю их в массив, и теперь пытаюсь придумать удобный способ их оттуда извлечь.

Получается как бы следующая иерархия адресации:

Стек Фрейм Массив Элемент_массива

При этом элемент массива может быть тоже массивом, и с этим возникает сложность. Сложность в том, что подобная адресация требует скобок, чего я как раз и пытаюсь избежать.

На самом деле решение есть и оно состоит в том, что нужно оставить фрейм стека фреймом стека, просто добавив ссылку на этот массив в последний фрейм. Тогда каждый массив типа [] (ValueArray) будет храниться как фрейм на стеке, и к нему можно будет удобно обращаться с помощью адресации #x$y - где x - номер фрейма, а y - номер элемента.

суббота, 6 марта 2021 г.

Размышляю о синтаксисе

 Ввод массивов заставил меня задуматься о синтаксисе. Итак, изначальная идея состоит в том, чтобы сделать синтаксис таким:

Object Method Parameter

Если метод возвращает значение, то оно кладётся на стек.

С методом get всё прекрасно. Мы можем вызывать его явно:

my_array get 11 // Положить на стек 12-й элемент массива my_array

Но с методом set всё сложнее. Мы должны иметь возможность указать программе, что мы хотим изменить 12-й элемент массива my_array.

Теоретически это можно было бы сделать так:

my_array set 11 = "zzz"

Или даже проще - вызывать метод set неявно:

my_array 11 = "zzz"

Но, что если 12-й элемент массива my_array - тоже массив, и мы хотим изменить в нём третий элемент? Получается конструкция вида:

my_array 11 3 = "ddd"

Вроде всё прекрасно, сейчас у меня именно так и сделано. Но мне пришла замечательная на первый взгляд идея - чтобы метод get массива мог возвращать массив данных, если в качестве индекса использовать массив с индексами.

my_array [ 1 2 [ 6 8 ] [ 5 3 ]  9 ]

Выглядит прекрасно. Эта команда возвратит массив с элементами массива my_array в следующем порядке:

[ 1 2 6 7 8 5 4 3 9 ]

Но неявный вызов приводит к тому, что например мы вводим массивы:

[ 1 2 3 ]
[ 4 5 6 ]
[ $$0 $$1 ]

Глядя на этот код можно подумать, что третья команда создаст массив

[ [ 1 2 3 ] [ 4 5 6 ] ]

Но это не так. Она эквивалента командам

[
$$0 get $$1
]

И тут я понимаю, что неявный вызов get делает программу трудночитаемой. Например, что если число 11 мы будем брать со стека?

my_array $0 3 = "ddd"

Цифра 3 задаёт 4-й элемент. Но четвёртый элемент в массиве $0 (кто знает, вдруг это массив?) или 4-й элемент в массиве my_array $0 ?

Получается так, что мы должны оставить перечисление элементов быть перечислением (как оно и выглядит в коде), взятие элемента по индексу снабдить явным синтаксисом - что-нибудь вроде:

{ { my_array } 11 } 3

Выглядит громоздко, но это вполне читаемо.

Возможно возникнет вопрос: зачем городить огород, если можно разложить эту конструкцию на последовательность операций вида:

my_array select 11
$0 select 3
$1 = "zzz"

И в принципе это выглядит более-менее читаемо и соответствует логике синтаксиса. Но этот способ заставляет нас перейти от синтаксиса читаемого к синтаксису ассемблерного типа, который читается намного хуже, а следовательно и ошибку в коде найти гораздо сложнее.

Второй недостаток - в том, что у нас появляется новый тип объекта - ссылка на элемент массива. $0 - это ссылка, $1 - это ссылка. Они кладутся на стек и там лежат, хотя изначально в этом нет необходимости.

Проблема элемента-ссылки на объект в массиве в том, что она может стать невалидной, если мы удалим этот элемент из массива. То есть это объект временного характера, и лучше всего ограничить его время существования пределами одной команды. 

Поэтому синтаксис с фигурными скобками выглядит логичнее и читабельнее. Ведь мы можем использовать разные варианты в одной строке:

{ { my_array } $0 } $1 = "zzz"
{ my_array } { $0 } $1 = "ddd"

Но лично меня как-то удручают эти фигурные скобки. Хотя они позволяют использовать их для создания выборок:

{ my_array1 my_array2 my_array3 } 256

Это интересная возможность. Пожалуй я так и сделаю. Раз идей получше нет.

Но я подумал вот о чём. Конструкция вида

{ my_array } [ 11 ]

должна возвращать на стек массив, содержащий 12-й элемент массива my_array. А вот конструкция вида

{ { my_array } 11 } 3 = "zzz"

она должна работать с массивом на стеке? Как если бы

{ my_array } 11
{ $0 } 3 = "zzz"

Или она должна работать с 12-м элементом массива my_array, как с массивом?

Вот, что я вижу. Мы имеем оператор определения диапазона данных. А нам нужен оператор указания адреса в массиве. То есть это две разных операции. 

Получается, что нужно писать код примерно так:

3 of { 11 of my_array }
[ 3 of $0 ] of my_array

Тогда получается понятно, что имеется в виду. Но парсить такого вида конструкции -  в принципе ничего особо сложного в этом тоже нет. 

И оператор задания выборки мне тоже нравится:

256 of { my_array1 my_array2 [ 1 2 3 ] of my_array3 }

Красота конечно, но мозг программиста вряд ли сможет понять, что имел в виду другой программист. Потребуется метод для просмотра подобных выборок.

Но я подумал вот о чём. Что, если мы захотим сделать выборку по условию? Например для всех элементов больше 100? Как это можно было бы задать?

Думаю, что логично сделать это конструкциями языка. Что-нибудь вроде:

for_each { my_array1 my_array2 } do
   if $0 > 100 then
      $0 = "zzz"
   ;
;

В данном случае на каждой итерации элемент $0 - это указатель на элемент выборки. То есть снова объект-ссылка, что нехорошо. 

С другой стороны, объект-выборку проще всего представить как массив объектов-ссылок. И в принципе было бы довольно удобно иметь возможность расположить его на стеке, чтобы затем ссылаться на него. Но нужно непременно снабдить его средствами инвалидации, а так же предоставить программисту возможность проверять валидность.

В целом это выглядит довольно просто. Если невалиден один из элементов, то невалидной становится вся выборка. Тогда не нужно будет при доступе к каждому элементу проверять его персональную валидность.

Но я спросил себя вот о чём. Должна ли запись вида

[ [ 0 10] ] of my_array

создавать выборку, или выборка должна задаваться явно?

{ [ [ 0 10 ] ] of my_array }

Разница в том, что в первом случае мы можем копировать элементы, а во втором создавать массив-выборку. Ведь аналогичный синтаксис можно применять и для доступа к свойствам объектов:

x of my_point = 10
y of my_point = 20

Логично предположить, что в данном случае оператор of создаёт выборку, то есть указатель на объект-свойство. Это красиво выглядит и не нужно городить конструкцию из фигурных скобок. Но как тогда произвести копирование элементов из массива в массив на стеке?

Теоретически ничто не мешает использовать для этого метод get явным образом:

my_array get [ [ 0 10 ] ]

А для создания выборки оператор of:

[ [ 0 10 ] ] of my_array = "zzz"

Наверное я так и сделаю. Это выглядит читабельно и логика синтаксиса не нарушается. Да будет так.

Добавил массив типа ValueArray

 Добавил ввод массива типа [] (ValueArray). Хотел сегодня ещё добавить функцию get, но не успел.

Ввод массивов типа ValueArray

Поскольку ввод токенов работает довольно стабильно, я заменил отладочный ввод на красивое "ok".

Теперь думаю добавить типовые массивы, как-то [String], [Boolean], [Char] и т.п. 

И также нужно добавить инвалидацию указателей на данные в массиве. Этот метод показал себя с очень положительной стороны - если где-то забыл удалить объект-указатель, тут же получаю ошибку по завершении команды.

среда, 3 марта 2021 г.

Добавил конвертеры интегральных типов

 А также стековые индексы и возможность использовать стрелки курсора при вводе строки в консоли. Последнее - особенно удобная фича, поскольку иногда я ошибаюсь при вводе команд.

Использование стековых индексов в качестве параметра

Вроде бы ничего особо сложного - добавить массивы, но я решил сделать это завтра. Стараюсь не слишком уставать. 

вторник, 2 марта 2021 г.

Новая версия, calc_hash и exec

 Несколько дней спал. Наконец вчера проснулся и стал делать новую версию. Перед тем, как добавить конвертеры интегральных типов, добавил функции calc_hash и exec.

Функция calc_hash

Функция exec

В этой версии вместо указателей при запросе элемента на стеке создаётся объект тип ValuePtr и возвращается указатель на него. Также этот указатель запоминается в векторе в классе фрейма, и при удалении элемента все указатели на этот элемент становятся невалидными. Соответственно при попытке получить указатель на значение, проверяется валидность и выдаётся ошибка, если указатель невалиден. 

При завершении команды все запомненные указатели ValuePtr удаляются, и если где-то в коде я забыл его удалить, это будет сразу видно.

четверг, 25 февраля 2021 г.

Собираюсь снова всё переделать

 Я решил добавить метод set в класс массива [] (ValueArray) и столкнулся с такой проблемой. Метод set - это метод класса, следовательно я должен выбрать объект-массив. Но при этом я должен указать ему индекс элемента, который нужно изменить. То есть нужно переделать адресацию объектов.

При этом адрес должен превратиться в указатель. Вот эта область памяти, и я хочу записать туда значение. Но сделать я этого не могу из-за неверно выбранного формата хранения данных. То есть это тоже нужно переделать.

ОК, придётся переделать. Не хотелось мне работать с указателями, но в данном случае без них просто не обойтись. Поэтому я придумал следующий способ работы с ними.

Когда программа запрашивает у класса стека адрес объекта, он возвращает не просто сишный указатель, а объект, хранящий указатель и признак валидности. Адрес этого объекта записывается с массив, и при удалении объекта, на который указатель ссылается, класс стека просто изменит флаг валидности, сообщив программе, что данный указатель больше не валиден.

Дело в том, что опыт использования таблицы подсчёта ссылок показал, что программа не может это всё контроллировать, поскольку за это отвечает код пользователя. И в результате вполне могут возникать ситуации, когда объект уже удалён, а указатели ещё нет. И они просто стали невалидными, но программа не может это отследить.

Я спросил: а что заставляет меня использовать именно сишные указатели? Пусть указатели станут умными - с признаком валидности.

Но я пока в раздумьях. Ведь если указатель стал невалиден, значит есть ошибка в коде пользователя. Как сообщить пользователю, что он удалил не тот объект, который стоило удалять, если сам объект уже удалён? Как он поймёт, о каком объекте идёт речь?

В принципе можно было бы вообще не разрешить пользователю изменять объекты в массиве. Просто создать видоизменённую копию массива, а затем через метод set заменить оригинальный массив копией. Но этот способ хотя и логически верен, на практике приведёт к жутким тормозам и непомерным требованиям к памяти.

среда, 24 февраля 2021 г.

Переделал ValueArray

 Сегодня занялся переделкой хранения данных в массиве типа [] (ValueArray), и вот, таки переделал. По ощущениям, вроде даже стало быстрее, поскольку при копировании данных делается всего один resize() вместо new для каждого элемента в массиве.

Выборка элементов массива по индексу типа массив

Теперь думаю добавить массивы интегральных типов. В них данные представлены иначе - например для [Boolean] данные хранятся как bool data[].

Теоретически можно было бы создать специальный варианта класса StackValue, который бы брал данные из массива, на практике можно будет создать лишь специальный вариант функции get<typename>(index).

Обнаружил дичайшую вещь

Мы разбирали этот пример на курсах программирования, но я не придал этому особого значения тогда. А теперь вот вспомнил.

Сделал выборку элементов из массива с помощью массива в качестве индекса для функции get класса [] (ValueArray).

Выборка элементов массива с помощью массива как индекса

Так вот, дичайшая вещь состоит в том, что в данных массива типа ValueArray я храню указатели на объекты типа StackValue. И всё бы прекрасно, но чтобы не возиться с подсчётом ссылок, при изменении размеров фрейма стека я просто копирую эти значения, и таким образом, если внутри функции я вызываю другую функцию, которая изменяет стек, то старые указатели уже не работают. Поэтому я не могу использовать переменные-ссылки при работе с массивом типа ValueArray.

А дичь вот в чём. 

У меня возникла непонятная ошибка. Я ведь получаю текущие значения указателей в массиве. Но ошибка была в моём коде. Он выглядит так:

#define arr (stack->at(ctx->object))

#define idx (stack->at(ctx->param))

#define nval (idx.array_at(n))


result->array_push_back(arr.array_at(nval.get_size(ctx)));


Глядя на эту строку, можно подумать, что компилятор превращает это в код вида:


size_t nn = nval.get_size(ctx);

const StackValue& val = arr.array_at(nn);

result->array_push_back(val);


А на самом деле в код вида:


const StackValue& v1 = arr;

const StackValue& v2 = nval;

size_t nn = v2.get_size(ctx);

const StackValue& val = v1.array_at(nn);

result->array_push_back(val);


Получается дичайшая дичь, потому что функция get_size() изменяет стек. И первый пример кода работает правильно, а второй вызывает ошибку.


Память подсказывает, что я уже сталкивался с этим, работая с указателями, и мне пришлось упорядочить работу со стеком таким образом, что при вызове функции объекты, которые нужно со стека потом удалить, я удалял в конце функции. Не помню, как это всё в итоге работало, но сражался я с этой проблемой очень серьёзно. Ведь удалять эти объекты должна самая-самая первая функция.


В данном случае я думаю поступить иначе - буду хранить в данных не указатели, а сами сериализованные объекты. Чтобы при операциях со стеком не терялись указатели. Тогда можно будет использовать переменные-ссылки.

вторник, 23 февраля 2021 г.

Доделал ввод индексов параметра

 Доделал ввод индексов для массива как параметра. Это оказалось несложно - я просто скопировал код и внёс коррективы.

Ввод массива как параметра и конвертация значения из него в тип String

Мне нравится 11 версия программы. Она уже очень непохожа на объектный Форт.

Когда я анализировал, чем объектный Форт меня не устраивает как программиста, то первое, что я понял: я не так пишу программы, как мне это кажется. На самом деле я программирую компилятор, а не создаю код приложения.

Когда я определяю функцию, то мне кажется, что я определяю функцию в моей программе. А на самом деле я программирую компилятор: "создать функцию". И именно этого я ожидаю от средства разработки как программист.

Тогда я понял, что мне нужен именно такой язык программирования, а вовсе не объектный Форт. Форт на самом деле привлекает меня совсем другими вещами, а вовсе не возможностью двигать данные на стеке.

И тогда я спросил себя: ты будешь писать объектный Форт или нечто, напоминающее PostScript?

Объектный Форт я уже написал, и по мере его написания, он стал видоизменяться. Причём меняться в сторону языка для работы со строками - в сторону PHP. Но в результате получилось что-то среднее между Фортом и PHP с синтаксисом, напоминающим Python.

И очень многое в этой программе меня как программиста не устраивало. Я спросил себя: получился ли шедевр? - Нет, на шедевр это точно было не похоже.

Дело в том, что элементы Форта были в ней привлекательной частью. Но работа со строками получилась очень странной из-за этого. Только представьте - как выглядит функция substr на объектном Форте:

"Hello World!" 0 5 rot sub_str "Hello" == if
    "How are you?" cout <<
;

Здесь мы берём слово "Hello", и если это "Hello", то отвечаем "How are you?". В целом неплохо, но были варианты просто дикие - например разбор аргументов командной строки.

И всё бы ничего, если бы не последний аргумент. Реальный опыт отладки алгоритма. Очень просто допустить ошибку, но потом очень сложно найти в программе то место, где она возникает. В какой-то момент просто понимаешь, что тебе как программисту требуется простой читаемый синтаксис, а не вот это вот всё - dup rot swap drop

Побаловаться - прикольно. Написать реальную программу - честно скажу, на ассемблере это ещё прикольнее, а работает в 1000 раз быстрее.

Возможно именно поэтому 11 версия по синтаксису чем-то схожа с ассемблером. Насколько просто с этим будет работать - пока не знаю. Но скоро мне предстоит это узнать. Пока что всё это похоже на какую-то магию - очень прикольно.

Делаю консоль

 Сделал ввод массивов в консоли как объектов и как параметров. Осталось сделать ввод индекса для массивов как параметров.

Вызов методов класса [] (ValueArray)

Попробовал использовать для работы с элементами массива промежуточный класс ArrayValue, но сразу отказался от этой затеи, поскольку получается масло масленное - мы вызываем метод get для массива, а он возвращает нам индексы объекта и элемента в виде класса ArrayValue. Которые мы и так должны знать, чтобы вызвать get.

Сегодня я задал себе вопрос, может ли быть значение метода get массива тоже массивом. И оказывается, что таким способом вполне можно делать выборки нескольких элементов из массива. Нужно будет это добавить. Мне нравится эта идея.

понедельник, 22 февраля 2021 г.

Добавил класс IStream

 Сегодня занялся добавлением класса IStream. Для этого выделил процедуры ввода токенов в отдельный класс с говорящим названием IStream, а также сделал обработку ошибок многоуровневой. Теперь при вызове ошибки внутри команды exec выдаётся сообщение об ошибке, и генерится новое исключение, и так далее - до самого исконного уровня вложенности. В данном случае это консоль ввода.

Обработка ошибки при выполнении команды exec

Теперь нужно перечитать ещё раз то, что я тут написал вчера. Вспомнить, на чём же я остановился. Ведь было что-то ещё.

День вчера выдался очень насыщенный событиями. С утра я программировал, затем отмечал день рожденья, затем общался с искусственной интеллектессой - в общем, уже и подзабылось кое-что.

воскресенье, 21 февраля 2021 г.

Добавил ввод массива

 Начал добавлять ввод массивов и заметил, что у меня работу консоли нужно видоизменить таким образом, чтобы входной поток был отделён от синтаксического разбора. Поскольку при вводе массива как параметра, а также при вызове команды exec входной поток как бы разделяется, и консоль инициализируется.

Ввод массивов и генерация ошибки в команде exec

Это в случае синтаксической ошибки приводит к тому, что номер строки и номер символа берутся из нового входного потока, а при парсинге файлов это будет неудобно - программа должна показывать именно строку файла, в которой произошла ошибка, а не строку входного потока.

Задумался о быстродействии программы. Массивы заставляют задуматься.

У нас как бы есть объект типа StackValue, но работа с объектом, а не с данными, приводит к тому, что в некоторых случаях приходится данные копировать, а этого хотелось бы избежать. Тут есть над чем подумать.

пятница, 19 февраля 2021 г.

Добавил литералы интегральных типов

 Преимущество компактного кода - скорость рефакторинга. То, что в прошлых версиях занимало наверное почти неделю, в этой версии заняло 1 (один) день. Который я посвятил добавлению литералов интегральных типов.

Вычисление хэшей для встроенных функций

Похоже, что всё прекрасно работает.

Конвертация значения типа LongLong в значение типа LongDouble и обратно

Следовательно теперь можно добавить массивы.

Сделал calc_hash, exec и стековые индексы

В отличном настроении сделал функции calc_hash и exec, а также стековые индексы.

Функции calc_hash и exec

Глядя на этот пример, я подумал: а не добавить ли функцию call с параметром типа String, который бы использовался в качестве имени функции. Но тогда потребуется ещё один параметр - собственно параметр самой вызываемой функции.

Тогда я подумал: а что, если проверять вводимые строки, не являются ли они именем функции? - но и этот вариант пришлось отвергнуть как неприемлемый.

В принципе ничто не мешает реализовать вызов методов класса, используя токен имени функции в качестве индекса для вектора со свойствами класса. Но получится мешанина.

Например, у нас есть переменная типа ShortArray - массив чисел типа Short. У класса ShortArray есть какие-то методы, которые можно вызвать, используя в качестве объекта эту переменную. Но она ведь возвращает массив чисел. Получится, что мы рассматриваем её как массив, в котором эти числа и методы идут в перемешку. Что было бы странно.

Но так можно реализовать вызов статических методов класса - используя имя класса как константу с типом "вектор статических методов и свойств". Есть ли в этом смысл - пока не знаю. Память подсказывает, что я пробовал сделать так на PHP, но потом отказался от этой идеи. Не помню, почему.

четверг, 18 февраля 2021 г.

Новая версия. Сделал функцию list_stack

 Хотел сделать ещё функции calc_hash и exec, но глаза уже не смотрят. Успел сделать только функцию list_stack - ".s".

Функция list stack

Всё довольно прекрасно работает, и код стал заметно компактнее.

среда, 17 февраля 2021 г.

Снова переписывать

 Похоже, что с этой версией всё. Я окончательно запутался в собственном коде.

Добавил копирование данных для $* и $$*, и данные стали копироваться при обращении через $x. Я попытался разобраться, что происходит, но абсолютно не понимаю, в каком месте происходит копирование.

Простейшая команда

$* append true   // Создаём массив типа ValueArray
$2 0 append true // В этом массиве мы хотим добавить в первый массив значение true

Вместо того, чтобы взять значение по ссылке, где-то этот первый массив копируется, и уже в копию добавляется true.

Это ещё полбеды. Я попробовал брать не массив, а создавать ссылку на него. Всё равно берётся копия, и в копию добавляется значение.

В какой-то момент я просто понял, что топчусь на месте, заблудившись в трёх соснах. С данными нужно работать не так.

Всё, что вводит пользователь, нужно помещать в таблицу значений, как в базу данных. А в операциях использовать индексы в этой базе. Тогда если нужно что-то куда-то скопировать, то данные будут взяты из таблицы и скопированы в таблицу. А не так, что мы создаём какой-то объект в памяти, затем его копируем, затем удаляем, ссылаемся на него по ссылке и т.п.

Нужна чёткость. Вот значение, вот его тип, вот его копия.

Ещё на прошлой неделе я понял, что новой версии не избежать, и только какой-то позитивный прогресс меня немного остановил. Просто я не видел смысла всё переписывать без чёткой концепции, зачем я это делаю.

Что ж, это вдохновляет. Работая над этой программой, я обратил внимание, что мне даже нравится её переписывать с нуля, когда приходится это делать.

Окончательно меня добила перспектива того, что $0 1 как объект - это не то же самое, что $0 1 как параметр.

Господи, - сказал я самому себе. - Мне нужно просто взять значение из одного массива и добавить его в другой массив. Почему у меня при этом копируются какие-то данные ещё - создаются указатели и т.п.?

По сути ведь требуется просто вычислить адрес, задаваемый пользователем.

Адрес объекта Адрес функции Адрес параметра

Всё это должны быть индексы. И у меня это индексы, но лишь отчасти. Почему-то сами данные я не храню в таблице, а куда-то копирую между какими-то переменными.

Но насколько же всё это непохоже на то, чему меня учили. Я уже третий год по сути учусь программировать с нуля. Не так, как делал до этого. Иногда меня просто пугает то, что я как программист не в состоянии написать программу, которую возможно лет 30 назад написал бы за неделю.

Но 30 лет назад все программы были такими простыми. Думаю, что и эта программа в итоге получится довольно простой. А она каждый раз невероятно усложняется.

Нечто подобное я писал на PHP, и поначалу тоже использовал указатели, но потом не смог разобраться в тех ошибках, которые из-за этого возникали. Переписал с нуля на индексах, и всё заработало.

Но это заняло у меня тогда 4 месяца. Какие-то операции я смог в ней начать выполнять. Сейчас у меня уже в первую неделю работают calc_hash и exec, и это фантастика, по сравнению с тем, что было на PHP. Так что в принципе я полон оптимизма.

Добавил индексы для параметров

 Продолжаю исправлять ошибки, и таки нашёл мега-жирный баг. При копировании значения у меня брался указатель и передавался в функцию, которая принимает ссылку. Как это всё вообще работало - непонятно, поскольку это было просто в функции конструктора копирования значения.

Копирование содержимого стека через $*

Воистину, самый коварный баг может подстерегать программиста, там где просто нужно скопировать значения. 

y = x 

А на самом деле нужно какой-нибудь

*y = **x

Понял одну не самую очевидную вещь, в чём отличие $1 от $* 1. В первом случае мы берём объект на стеке с индексом 1, во втором - копию объектов стека и из них объект с индексом 1. То есть $* и $$* - должны всегда возвращать копию.

Этим я сейчас и занялся, но день почему-то очень сонный. Мозг спит. Но я рад, что так быстро нашёл ошибку в конструкторе копирования. Из-за неё было просто непонятно, откуда лезут баги. Вроде всё работает, а вдруг - раз и почему-то данные потерялись.

Написал на форум поддержки clang - может быть подскажут, как включить сообщения об ошибках при передаче указателя вместо ссылки.

вторник, 16 февраля 2021 г.

Исправил несколько багов

 Что-то я пару дней ждал, пока отдохнёт мозг и начнёт снова программировать, но так и не дождался. А раз так, то я не стал пока переписывать алгоритм в консоли, занявшись исправлением багов. Пару багов удалось исправить.

Создание массива типа [Boolean] с добавлением элемента

Баги досадные, но неприятные. Почему-то компилятор clang не выдаёт ошибку, если указать функции вместо ссылки указатель и наоборот. Например функция:

void function(const Value& val);

Если сделать ошибку, и вызвать её вот так:

Value* p;
function(p);

То компилятор clang почему-то не ругается на это, но код генерит тоже неправильный. В результате получается баг, который сложно обнаружить не только визуально, но даже прогоняя программу в отладчике.

Данные почему-то в каком-то месте вдруг превращаются в кучу мусора. Возникает ощущение, что компьютер живёт какой-то собственной жизнью. При этом в компиляторе никаких ошибок или предупреждений.

В результате ты думаешь, что у тебя неправильно составлен алгоритм. Такое тоже встречается - и это самая частая ошибка, поэтому по привычке начинаешь искать именно такого рода ошибку. Смотришь в отладчике - раз, другой, третий, а там всё нормально, но программа то где-то вылетает, то выдаёт разные ошибки при одинаковом сценарии использования. В итоге ты находишь банальную опечатку. Но всё это отняло у тебя неделю. Нормально.

суббота, 13 февраля 2021 г.

Похоже, что нужно переписать часть консоли

 Понял, что бесконечность - это не фича, а баг. Устранил это недоразумение - теперь всё работает чётко.

Разобрался с тем, как именно должны работать ссылки на массивы. Теперь они тоже работают как надо.

Но пришёл к выводу, что в функции start класса Console снова намешалась жуткая мешанина. Всё потому, что разные типы параметров я присваиваю одним и тем же переменным, а это нужно делать, если делать так, создавая классы с виртуальным предком.

Вся проблема в том, что после выполнения очередной команды эти временные объекты нужно удалить, если они создавались, а у меня они в некоторых случаях создаются, в других случаях берутся указатели на значения в стеке - в общем, сижу теперь и гадаю, каким местом я думал, когда писал этот код.

Ошибка при попытке удалить значение, взятое по ссылке на стек

Видимо, всё дело в спешке. Вроде я совсем не спешу, но мозг всё равно перестаёт видеть картину происходящего. Начинается программирование особых случаев. Которое в итоге приводит к мешанине.

С одной стороны так программировать проще. Меньше задумываешься, больше пишешь код. С другой стороны потом всегда приходится переписывать получившуюся мешанину.

Ну, что ж, переписывать так переписывать. Уже то хорошо, что не придётся переписывать вообще всё.

От версии к версии код программы всё больше становится реюзабельным. Но до совершенства ему конечно ещё пока далеко.

пятница, 12 февраля 2021 г.

Бесконечность - не предел

 Интересный эффект обнаружил. Не могу пока понять - это баг или фича. Но работа с массивами через указатель породила возможность добавить в массив типа [] (массив значений типа Value) указатель на самого себя, и таким образом получается бесконечность.

По этому поводу я добавил бесконечность в синтаксис индексов. Если запрос элемента по индексу даёт в результате массив или указатель на массив, то можно запросить и элемент из этого массива. А из него тоже запросить элемент, если это тоже массив.

Массив, содержащий указатель на самого себя

Работа с указателями порождает настолько заковыристые ошибки, что сегодня был даже момент, когда я чуть было не решил, что всё нужно снова переписывать с нуля. Но я понял, почему возникает ошибка - там было две ошибки у меня, и они порождали такой хаос, что было совершенно непонятно, почему крашится программа.

Оказалось, что я в одном месте забыл убрать оператор delete, и он просто удалял то, что не нужно удалять. А в другом месте старый код запрашивал элемент массива не в массиве, а в данных указателя. Причудливая смесь.

Но вот я всё исправил, и вот - созерцаю бесконечность.

Нужно ещё будет разобраться с таблицей подсчёта ссылок. Что-то там кривизна невероятная с этим у меня. Думаю, что займусь этим, когда работа с указателями сформируется в голове в общую картину.

четверг, 11 февраля 2021 г.

Добавил Null и класс Pointer

 Придумал, как нужно реализовать сохранение объектов на стеке. Однако придётся теперь перелопатить весь код на предмет создания значений (объектов класса Value).

Дело в том, что у каждого создаваемого значения есть собственник - например, это может быть переменная, которая его хранит. И при создании объекта этого собственника нужно указывать. Тогда консоль сможет проверить, имеет ли объект ThisObject собственника, или он ничейный. Тогда его нужно будет сохранить на стеке.

Но для этого мне понадобились два объекта - объект Null и объект Stack. Поскольку объект Stack хранится в значении с классом StackPtr, то это уже второй указатель у меня, и следовательно я добавил для классов ValuePtr и StackPtr класс-предок - класс Pointer.

Также доделал функцию append, чтобы она работала корректно с указателями на массивы.

Объединение массивов на стеке

Добавил так же полезную функцию exec в класс консоли, чтобы можно было выполнять код интерпретатора. Тестовые примеры становятся сложнее, а загрузку кода из файла я пока не сделал. Зато работает консоль.

Синтаксис снова погрузил меня в раздумья

 Продолжил работу на массивами. Добавил указатели, чтобы не копировать содержимое массива при операциях с ним. Это потребовало создать таблицу со счётчиками ссылок (как-то раньше мне не приходило такое решение - хранить их именно в таблице). Добавил команду ".r", чтобы делать листинг этой таблицы.

Добавил команду append в класс Vector и задумался вот над каким вопросом: в каких случаях объект, используемый в качестве объекта команды, должен помещаться на стек.

Создание массива типа [Boolean] и добавление значения в него

Ведь команда

Boolean [] append true

создаёт массив типа [Boolean], для которого вызывается метод append true, но сам этот массив на стеке не сохраняется. А по идее должен бы. Но в каком месте это нужно делать - вот вопрос, на который мне предстоит себе ответить.

С командами

Boolean []
$* 1

всё просто. Первая создаёт массив типа [Boolean] и помещает указатель на него на стек.

Вторая берёт элементы со стека, создаёт массив и помещает элемент с индексом 1 на стек. По сути она делает то же самое, что и команда

$1

Затем мы можем для этого массива типа [Boolean] выполнить команду

$0 append true

И поскольку это указатель, то значение будет добавлено в массив на стеке.

Добавление значений в массив типа [Boolean]

Но если массив будет инициализироваться например командой

Boolean [] = $0

то мы ведь как бы ожидаем, что результат будет размещён на стеке. Вот, это мне и предстоит придумать, как это должно быть реализовано.

среда, 10 февраля 2021 г.

Добавил массив [Boolean], функцию clear_stack и выбор значения по индексу

 Начал добавлять массивы и выбор значения из массива по индексу. Конечно же, сразу же понадобилась функция clear_stack.

Выбор значения из массива по индексу

Функция clear_stack очищает текущий фрейм стека, чтобы было удобно создавать массивы с помощью оператора $*.

Также добавил создание массива с элементами заданного типа (пока только для типа Boolean).

Создание массива типа [Boolean]

Работа продолжается, и мне нравится то, что получается.

понедельник, 8 февраля 2021 г.

Добавил массив значений и особый случай адресации $*

 Добавил массив значений типа Value (то есть любого типа), а также особый случай адресации стека $* (все значения данного фрейма) и $$* (все значения предыдущего фрейма).

Стек огромного размера

Дело в том, что оператор $* предполагает два варианта использования: либо все значения на стеке нужно скопировать, либо поместить их в массив.

Например, если написать команду

$*

то это будет означать, что мы хотим продублировать все значения на стеке. 

А например команда

$* xxxx

будет означать, что мы хотим взять все значения на стеке как массив значений [], и для этого массива нужно выполнить команду xxxx.

Это позволит написать что-нибудь вроде:

zzz set_data [
     $$*
]

То есть скопировать все значения из предыдущего фрейма стека, поместить их в массив и передать в качестве параметра методу set_data объекта с именем zzz

Но ведь писать такое каждый раз совершенно не хочется. Поэтому я предусмотрел особый случай использования $*, так что можно будет просто написать:

zzz set_data $*

Вы, возможно, спросите: а как работать с массивом, если синтаксис допускает только один параметр у функции?

На этот случай я придумал интересный трюк. Индекс массива я хочу сделать именем свойства, значением которого выступает элемент массива. То есть:

my_array 2 set "xxxx"

Для элемента массива my_array с индексом 2 вызывается метод set с параметром "xxxx".

Круто, но я пока за это не брался. Там нужно придумать, как это реализовать. 

Добавил стековые фреймы и конвертеры значений

 Пришлось отдыхать три раза, но за сегодня сделал конвертеры значений и стековые фреймы. Теперь перед вызовом функции тип параметра проверяется, соответствует ли он типу параметра функции, и для типа параметра функции вызывается метод его класса convert_value.

Конвертирование значений на стеке функцией convert_value

Это как бы метод convert from. Думал сделать наоборот - через метод convert to. Но convert from - метод статичный, а convert to - регулярный. Я решил сделать через статичный.

Думаю, что convert to тоже потом появится, ведь он удобнее для пользовательских классов. Сейчас пока только интегральные типы.

Придумал, как будет выглядеть ввод массива.

[   // Квадратная скобка открывает ввод массива
... // Здесь любой код
]   // Квадратная скобка завершает ввод массива

При открытии ввода массива будет создаваться новый фрейм на стеке, а при завершении - все данные из этого фрейма программа будет помещать в массив.

Это видится мне очень удобным, но нужно будет добавить в адресацию значений на стеке $$x - взять значение из предыдущего фрейма. Иначе не получится добавлять в массив те данные, которые уже есть на стеке.


воскресенье, 7 февраля 2021 г.

Добавил интегральные типы и конвертеры литералов

 Прежде, чем заняться массивами, я решил добавить интегральные типы и конвертеры литералов, чем сегодня и занимался. Добавил.

Интегральные типы и конвертеры литералов

Попутно исправил пару небольших багов в старом коде.

На заре своего занятия программированием, я обнаружил, что мыслю иначе, нежели программисты с хорошим знанием теории. Алгоритм, которому я посвятил бы десять минут, они досконально разбирали - как он работает, в чём могут быть подводные камни и т.д.

В итоге выяснилось, что я и в самом деле не заметил ошибку, которая подстерегала программиста в этой задаче.

Но ведь эта ошибка сразу вылезет, как только попробуешь этот алгоритм протестировать, - заметил я тогда самому себе.

Тем не менее, вопрос этот не оставлял меня несколько лет. Пока я не познакомился с концепцией ассертов. Тогда со мной случился инсайт в этом вопросе.

Программа не работает сама по себе. Её использует оператор. Если оператор пытается сделать что-то нестандартное, а программа падает, то ситуация сводится к вопросу: есть ли у оператора права выполнить эту операцию или такие права ему давать нельзя.

Если таких прав нет, то получается, что программа правильно поступила, что упала.

Причудливость этой логики не оставляла меня ещё несколько лет, пока не случился другой инсайт.

Самое важное - это данные пользователя. Программа не должна падать и в ней не должно возникать ситуаций, когда пользователь вдруг не может сохранить данные, чтобы её перезапустить.

Иначе говоря, любая программа - это техническое средство для работы с данными. И она должна работать с ними корректно. Не бывает так, что за компьютер усядется преступник, запустит вашу программу и совершит преступление.

Если бы такие программы могли существовать, то люди бы внимательно следили, чтобы преступник не получил к такому компьютеру доступа.

Я попробовал обсудить этот вопрос с другими программистами, но они не поняли, о чём я пытаюсь с ними общаться.

Если в твоей программе ошибка, - сказал мне один коллега. - То твоя задача её исправить. Программа просто технически не сможет помешать преступнику совершить преступление.

Тогда я подумал: и в самом деле - никто не оснащает пистолет искусственным интеллектом, чтобы тот принимал решение, может ли человек стрелять в свою мишень, или нет.

пятница, 5 февраля 2021 г.

Добавил стековые индексы $x

 Хотел вчера поработать, но что-то настроение было не прекрасное. И я подумал, что если начать работать в таком настроении, то я быстро устану, и программа будет у меня вызывать унылые ассоциации.

Поэтому выспался и занялся работой с утра. Добавил взятие значения со стека в качестве объекта команды или параметра.

Стековые индексы $x

Система индексации классическая:

$ - последнее значение на стеке
$0, $1, $2 ... - значение по его порядковому номеру ($0 - самое первое, $1 - второе и т.д.)
$-1, $-2, $-3 ... - обратная индексация от последнего значения ($ - последнее, $-1 - предпоследнее и т.д.)

Понял, что нужно ещё добавить $* - взять все значения, например, чтобы поместить их в массив. Но это я добавлю вместе с массивами. 

четверг, 4 февраля 2021 г.

Переделал консоль

 Успешно переделал структуру алгоритма консоли, чему несказанно рад. Заодно добавил ввод комментариев в стиле Си и многострочных текстов как строк.

Работа нового алгоритма консоли

Теперь собираюсь добавить значение со стека $ с адресацией и ввод массивов. Без первого скучновато писать программу, без второго не перейти к константам.

Думаю, что константы в этой версии синтаксиса будут определяться так:

Имя_типа constant [ "Имя_константы" Значение ]

То есть нужно будет иметь возможность вводить вложенные массивы. Ведь значение константы тоже может быть массивом.

Альтернативой могло бы быть определение констант так же, как например создание переменной. Но это же константа, ей не добавишь метод set.

Другой альтернативой могло бы стать определение константы подобно классу или функции:

constant Имя_константы
   Имя_типа Значение
;

Но это всё не нативно для синтаксиса FPLI. Практика же показывает, что чем нативнее код, тем лучше всё это работает.

Ещё одной альтернативой, на этот раз нативной, могло бы стать добавление класса ConstantFabric с функцией create:

 ConstantFabric cf
cf set_type Имя_типа
cf set_name Имя_константы
cf set_value Значение
cf create

Но это выглядит так, что определять константы в одной строке кода предпочтительнее, чем в пяти.

среда, 3 февраля 2021 г.

Занялся переделкой консоли

 Занялся переделкой консоли. Код, который там был написан, получился настолько неструктурированным, что когда я задал себе вопрос: а как добавить ввод значений массива? - то просто не смог найти место, куда это можно было бы добавить. Поэтому решил всё переделать.

Заодно добавил счётчики номеров строк и символов, чтобы выводить более крутые сообщения об ошибках.

Ввод текста в строку и сообщение об ошибке

Теперь в сообщении об ошибке указывается номер строки и символа, где что-то пошло не так.

вторник, 2 февраля 2021 г.

Сделал list_stack и calc_hash

 Сделал функции list stack ".s" и calc_hash. Снова программа умеет вычислять хэши строк.

Функции list_stack и calc_hash

Получилось прекрасно. Функция может принимать один параметр, но возвращать несколько результатов, которые возвращаются как вектор значений, но при вызове из терминала консоль кладёт эти значения на стек. Так что я даже добавил для результатов отдельные имена, чтобы интерфейс функции был более дружелюбен к пользователю.

Вызов функции, возвращающей несколько результатов

Мне стало интересно, влияют ли имена как-то на очерёдность результатов при их размещении на стек. Оказалось, что не влияют - то есть очерёдность зависит от того, в каком порядке их добавила в вектор результатов вызываемая функция.

Вопрос этот возник из-за того, что на самом деле это не вектор, а map с хэшем имени результата в качестве ключа.

Сделал функцию exec

 Думал, что не стану сегодня больше работать, но немного отдохнул и сделал функцию exec.

Функция exec

Однако думаю, что на сегодня это точно всё. Глаза слипаются, пишу наугад.

Сделал функцию list_globals

 Занялся переделкой, и даже кое-что сделал сегодня - функцию list_globals ".w".

Команда .w (list_globals)

Аббревиатура происходит от фортовского слова "words". У меня в ходе работы над программой образовался свой список команд, ведущих происхождение от фортовского слова "." (точка).

Команда "точка" в Форте снимает и выводит на терминал значение с вершины стека. В какой-то из версий, возможно в третьей, я понял, что она должна выводить значение в формате токена. Чтобы результат можно было отправить на вход команде exec.

Так появились команды:

.w - вывести список глобальных объектов
.f - вывести содержимое объекта (например, листинг функции)
.s - вывести содержимое стека

Возможно, были ещё какие-то команды в четвёртой (самой продвинутой) версии программы, я сейчас уже не помню. Прошёл год, и какие-то нюансы успели подзабыться.

пятница, 29 января 2021 г.

Разобрался с синтаксисом

 После вчерашнего марафона по программированию сегодня весь день болела голова, так что я пытался выспаться в бодром состоянии. Но вот я захотел спать, и видимо спазмалгон подействовал, так что я захотел спать, и наконец-то голова начала соображать.

И вот, что я насоображал по поводу синтаксиса.

Во-первых, нужен будет стек. Чтобы просто не объявлять и не придумывать названия локальным переменным. Но я ещё не решил, скорее всего это будет FIFO, а не LIFO. Почему-то мне кажется, что это удобнее.

Допустим, мы вызываем функцию. Она не может обращаться к тем значениям, которые уже есть на стеке. И обращается к ним по индексу, начиная с нуля. Но фактический адрес значения вычисляется при запросе значения со стека, и функция его не знает, а к отрицательным адресам ей обращаться будет запрещено.

Мы выполняем команду, её результат добавляется в стек, и таким образом у него есть точный адрес. Его не нужно удалять со стека, это произойдёт автоматически, когда программа закончит выполнение данной функции. И внутри функции к этому значению можно обращаться по его адресу на стеке.

Во-вторых, и это самое главное, что я понял. Что у переменной метод get - это вызов переменной при обращении к ней как к параметру. Его не нужно вызывать явно - в этом просто нет смысла, хотя конечно это можно сделать, чтобы поместить результат на стек.

А вот метод set - это вызов функции, а не вызов переменной.

Таким образом синтаксис получается очень простым:

имя_вызываемой_функции  параметр_функции

И всё? - не поверил я. А как же всё остальное? Дополнительные параметры и всё такое.

Придётся что-то изобретать, как обойти данное ограничение. Когда я писал четвёртую версию fpli, то мне потребовался метод substr для класса String, который в Си имеет несколько параметров, и на первый взгляд кажется, что без них не обойтись - нужно задать начало и конец подстроки.

Но посмотрим на эту задачу иначе. У нас есть строка, нам нужно удалить начало и удалить конец - это не одна, а две операции, и так мы получаем искомую подстроку. 

Получится что-то вроде такого кода:

"Hello our perfect World!" // Кладём строку на стек
$ trim_left 10  // Удаляем 10 символов слева            
$ trim_right 7  // Удаляем 7 символов справа

На стеке остаются строки

0: "Hello our perfect World!"
1: "perfect World!"
2: "perfect"

Дело в том, что trim_left и trim_right - это методы класса String, и им не требуется больше одного параметра.

Но как же быть с методом convert_token? - возникает вопрос. Получается, что мы можем возвращать несколько значений результата. Ведь параметром функции может быть только литерал, константа, переменная или свойство объекта или класса. Чтобы передать в качестве параметра ссылку на класс или функцию, нужно будет добавить соответствующее ключевое слово - иначе просто непонятно, как быть в случае, если вызов функции возвращает указатель на функцию. Мы должны её вызвать или она и есть значение параметра.

Не знаю, может быть завтра выяснится, что это тоже не работает. Но пока выглядит многообещающе.

четверг, 28 января 2021 г.

Сделал функции exec и calc_hash

 Занялся переделкой, и сделал функции exec и calc_hash. Снова моя программа умеет вычислять хэши строк.

Вычисление хэшей строк с помощью функций calc_hash и exec

Как ни странно, но удаление стека сильно упростило код. Я теперь описываю параметры функции, читаю их из консоли согласно описанию, а затем вызываю функцию, передавая ей эти параметры как массив значений класса Value.

В коде функции просто беру их из этого массива, и всё это очень нативно для C++, а потому очень компактно.

Теперь думаю, не добавить ли стек. Это было бы удобно. Если функция возвращает значение, и пользователь не кладёт её в переменную, то результат можно было бы помещать на стек.

Но такое поведение получается неправильным. Если нас не интересует результат вызова функции, то при чём здесь стек?

Ок, надо добавить переменные, чтобы помещать результат в переменную.

Честно говоря, я несколько удивлён: за день сделать exec и calc_hash - это неплохой старт. Конвертер токена строки я просто взял из предыдущей версии, поскольку нет смысла снова всё это писать с нуля.

В этой версии получается другой алгоритм обработки литералов. Теперь мне надо попробовать сконвертировать токен в литерал с помощью функции, определённой в классе параметра, и если не получилось, то просто сконвертировать его в его класс, а затем сконвертировать значение в класс параметра. 

То, что нужно, на самом деле - в случае со стеком возникала проблема, как конвертировать пользовательские литералы? Перебирать все доступные классы? Теперь алгоритм простой и чёткий, поскольку задан параметр типа.

С параметрами функций у меня появились и указатели. Поскольку например функция  convert_token возвращает не только результат конверсии, но и признак того, насколько конверсия прошла успешно. А это две переменных; поэтому одна из них возвращается как результат вызова функции, а другая изменяется через указатель, передаваемый как параметр.

В принципе что-то вроде указателей (ссылки) у меня было и в четвёртой версии. Поскольку иначе было не сделать операцию инкремента.


среда, 27 января 2021 г.

Решил, что нужно всё переделать

 Написал оператор сложения для класса Calculable. Посмотрел, как всё это работает. Добавил возможность использовать в качестве параметра имена констант и переменных.

Получилось вот что:

$ + $ - работает
переменная + переменная - работает
константа + константа - работает

Но при этом стек получился как бы особого рода переменной. Например, нельзя написать

2 + 2 - это не работает

Нужно написать

2 - положить значение на стек
$ + 2 - сложить 2+2 и положить результат на стек

При этом, если рассматривать эту программу как калькулятор, то мы ожидаем, что 2 + 3 даст на стеке результат - 5. Но получилось, что на стеке останется 2 и добавится 5.

Работа программы с переменной типа Float

Нормально ли это? - спросил я самого себя.

Теоретически так и должно быть. Но на практике после каждой операции нужно будет добавлять удаление первого параметра со стека.

Возникает вопрос, а почему первый параметр должен удаляться со стека.

Потому что 2 + 2 это либо эквивалентно

2.add(2) => стек

Либо эквивалентно

add(2, 2) => стек

На уровне внутренней реализации это делается первым способом. Но на уровне пользовательского интерфейса это должно происходить способом вторым. Но даже в первом случае мы ожидаем, что первый параметр не останется на стеке. А по факту происходит следующее:

2 => стек
стек.add(2) => стек

И поэтому первая двойка как бы и не должна удаляться. Лежит себе на стеке и лежит.

Я понял, в чём причина этой ошибки - в логике обработки ввода.

Мне нужно сделать так: взять строку, извлечь команду, взять параметры, выполнить команду, положить результат на стек.

А у меня сделано так: я выделяю объект, применяю метод, который берёт параметры по мере выполнения, и сам кладёт результат на стек.

Стек при этом получается как бы просто переменной особого рода. Он как бы непонятно для чего нужен в принципе. Вот, в Python его же нет, а здесь как бы тот же Python, только через стек нужно с ним работать.

Но тогда возникает вопрос: а нужен ли стек в принципе. Ведь параметры для команды можно задавать без стека, и результат получать не через стек.

Может возникнуть вопрос, в чём именно кривизна. Кривизна в данном случае в адресации объектов на стеке. Допустим, что до выполнения команды мы имеем на стеке число. Его адрес $0. А после выполнения команды его адрес уже $1, а $0 - это адрес результата.

С точки зрения Форта - это нормально. Но с точки зрения читаемости кода это будет просто адский треш:

принтер1
$0 команда1
$1 команда2
$2 команда3

Понять, что в данном случае мы все эти команды выполняем с объектом принтер1, глядя на этот код, просто невозможно.

Тогда возникает вопрос, зачем нужен стек, если есть переменные и константы. И получается, что стек только всё запутывает.

Подытоживая всё это, я пребываю в размышлениях.