Каким должен быть язык программирования? Анализ и критика Описание языка Компилятор
Отечественные разработки Cтатьи на компьютерные темы Компьютерный юмор Прочее

Об одной реализации специализированных операторов ввода-вывода

Введение

Введение

            Как-то на одном из компьютерных форумов участники делились соображениями, чего им не хватает в языках, так сказать, в повседневной деятельности. Один заявил, что ему очень не хватает оператора типа put data. Для тех, кто не слышал о таком, поясню, что в языке PL/1 был предусмотрен оператор вывода (или ввода) значений переменных вместе с их именами, т.е. вместе с идентификаторами. Например, если обычный вывод put list(X,Y); давал что-нибудь вроде:
1280    1024
то оператор put data(X,Y); печатал:
X=   1280  Y=   1024
не заставляя писать для этого вывод в виде оператора:
put list(’X=’,X,’Y=’,Y);
            Чаще всего это использовалось в отладочных целях, но не обязательно. Например, используя вывод в текстовый файл с помощью put data, а затем ввод этих же данных как исходных в другой программе с помощью «зеркального» оператора get data можно было получить удобную для чтения и редактирования форму представления исходных данных в текстовом виде.

            Можно возразить, что современные «оболочки» систем программирования легко позволяют вывести значения (и имена) любых объектов программы в интерактивном режиме безо всяких put data и поэтому в нем больше нет необходимости.

            Я тоже использую встроенный интерактивный отладчик, с помощью которого можно посмотреть любые, даже сложные (составные) данные. Однако потребность в возможностях put data не исчезла. Иногда нужен и анализ данных на распечатке без необходимости сидеть за компьютером, и быстро вставляемый вывод для отладки. К тому же для работы отладочных интерактивных систем используется дополнительная информация, которая обычно в выполняемом exe-файле отсутствует. В этом же случае все имена переменных «зашиты» в самой программе. Таким образом, удобства, предоставляемые языком в виде оператора, подобного put data, не заменяют, а дополняют возможности отладчиков. Кстати, в стародавние времена так называемая «посмертная» выдача на печать после ошибки иногда включала в себя и вывод полного списка всех имен переменных вместе с их текущими значениями. Некоторым программистам так нравился такой вывод, что они специально в конце своей программы ставили деление на ноль.

Реализации ввода-вывода с именами

            В исходном варианте компилятора, которым я пользуюсь и который по мере сил развиваю [1], оператора put data не было. И мне его тоже очень не хватало в процессе отладки. Требовалось его ввести, причем хотелось сделать все как можно проще, компактнее и не внося больших изменений в компилятор. Поэтому была принята следующая схема реализации:

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

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

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

            Для реализации put data достаточно проанализировать запомненные в буфере лексемы, найти конец разбираемого элемента (это запятая вне скобок или внешняя закрывающая скобка) и все предыдущие лексемы объединить в одну строку, которую в начале работы эмулятора и выдать с признаком «текстовая константа».

            В конце этой автоматически сформированной строки для наглядности дописывается знак равенства. После выдачи лексемы-строки эмулятор еще выдает лексему «запятая», а затем начинает выдавать все лексемы из буфера. По исчерпанию лексем в буфере эмулятор вместо себя опять подключает «настоящий» лексический анализатор, т.е. тот, который продолжит чтение исходного текста, и компилятор становится готов к разбору следующего объекта ввода-вывода. Таким образом, например, текст:
put data(x(i+1,j-1));
внутри компилятора будет восприниматься как:
put list(’x(i+1,j-1)=’,x(i+1,j-1));
            Разумеется, так будут обрабатываться не только объекты-переменные, но и любые выражения при выводе. Исключение составляет лишь вывод текстовых констант. Для них дополнительные лексемы, конечно же, не формируются во избежание бессмысленного вывода одного и того же текста два раза подряд со знаком равенства посередине.

Ввод-вывод не скалярных объектов

            Идея исправлять исходный текст программы «на лету» позволила легко решить поставленную задачу. Однако полученный механизм жаль было использовать только для такой мелочи как put data, поскольку этот же подход можно было применить и для более объемной задачи — ввода-вывода не скалярных объектов. Автоматический ввод-вывод всех элементов не скалярного объекта также был предусмотрен в языке и тоже отсутствовал в исходной версии компилятора, хотя на практике часто требовался.

            Здесь ситуация сложнее, чем с put data для скаляра, так как для вывода массива вместо текста:
put data(x);
требовалось «на лету» сформировать уже что-нибудь вроде:
do i=lbound(x) to hbound(x); put data(x(i)); end;
Но и это еще не все. Например, если x — двумерный массив целых (например, матрица 3х3), тот же самый оператор put data(x); уже должен осуществлять вывод по двум циклам, чтобы дать в результате что-то такое:
X(1,1)=    1 X(1,2)=    2 X(1,3)=    3 X(2,1)=    4 X(2,2)=    5 X(2,3)=    6 X(3,1)=    7 X(3,2)=    8 X(3,3)=    9
Но при этом оператор put data(x(2)); должен вывести лишь одну строку матрицы:
X(2,1)=    4 X(2,2)=    5 X(2,3)=    6
А если x — это набор разнотипных элементов (в PL/1 такой набор называется структурой), то нужно еще выводить и имена отдельных полей этого набора, например:
declare
1 a,
 2 a1  fixed,
 2 a2  fixed,
 2 b,
  3 b1 fixed,
  3 b2 float;

put data(a);

A.A1=      0 A.A2=      0 A.B.B1=      0 A.B.B2=  0.000000E+00
Учитывая, что в структуре могут быть подструктуры, в свою очередь включающие массивы (т.е. могут быть и структуры массивов и массивы структур), то задача автоматического вывода элементов становится довольно громоздкой:
declare
1 a(1:2),
 2 a1         fixed,
 2 a2         fixed,
 2 b,
  3 b1 (-5:3) fixed,
  3 b2        float;

put data(b);

B(1).B1(-5)= 0 B(1).B1(-4)=         0 B(1).B1(-3)=      0 B(1).B1(-2)=      0 
B(1).B1(-1)= 0 B(1).B1(0)=          0 B(1).B1(1)=       0 B(1).B1(2)=       0 
B(1).B1(3)=  0 B(1).B2=  0.000000E+00 B(2).B1(-5)=      0 B(2).B1(-4)=      0 
B(2).B1(-3)= 0 B(2).B1(-2)=         0 B(2).B1(-1)=      0 B(2).B1(0)=       0 
B(2).B1(1)=  0 B(2).B1(2)=          0 B(2).B1(3)=       0 B(2).B2=  0.000000E+00
Однако в языке PL/1 есть хорошая основа для доработок в виде возможности записать оператор цикла прямо внутри оператора ввода-вывода. В свое время разработчикам языка вывод массивов казался очень важным. Поэтому они разрешили писать цикл ввода-вывода в виде:
put list((x(i) do i=lbound(x) to hbound(x)));
вместо обычного цикла:
do i=lbound(x) to hbound(x); put list(x(i)); end;
Признаком начала цикла внутри оператора ввода-вывода является открывающая скобка. Кстати, разработчики PL/1 здесь немного попали впросак, поскольку при выводе выражения, начинающегося со скобки, большинство компиляторов воспримет эту скобку за начало цикла. Программистам приходилось писать выражения в экзотическом виде, только чтобы они не начинались со скобки:
put list(1e0*(x1+x2)/2e0));
            Тем не менее, это сильно упрощает доработку компилятора, поскольку для вывода всех элементов массива достаточно при разборе очередного объекта ввода-вывода поставить его в скобки, дописать индексы и написать в этих же скобках заголовок цикла. В заголовке цикла начальное и конечное значение переменной цикла — это константы, которые берутся из описания не скалярного объекта. В данной версии компилятора разрешено только статическое описание границ массивов и это также сильно упрощает доработку.

            Легко заметить, что вывод с именами (put data) и вывод не скалярных объектов — это дополняющие друг друга возможности. Объединяет их то, что в обоих случаях разбор объекта при трансляции выполняется два раза. Сначала все лексемы при лексическом разборе объекта запоминаются, а при повторном разборе выдаются из буфера так, как если бы из исходного текста. Но при этом в нужный момент выдаются и новые лексемы. В обоих случаях вместо лексического анализатора работает его эмулятор. Когда нужны обе возможности одновременно — работают оба эмулятора, один на более низком уровне выдает лексемы для индексов, названий полей структур и заголовков циклов, а второй добавляет к ним элементы с текстовыми константами-именами.

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

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

            Естественно, что если в исходном тексте старшие индексы объекта были явно указаны, то эмулятор дописывает только недостающие младшие индексы. Поэтому, например, для трехмерного массива x(1:10,1:10,1:10) оператор вывода:
put list(x); выдаст 1000 значений
put list(x(5)); выдаст 100 значений
put list(x(5,6)) выдаст 10 значений.
            А для оператора put list(x(5,6,8)); эмулятор вообще не сгенерирует дополнительных лексем-индексов и будет напечатано единственное значение. Недостатком описанной реализации оказалось то, что при применении оператора put data к не скалярным объектам на печати вместо всех значений индексов печатались идентификаторы переменных циклов, которые подставлялись в исходный текст в момент трансляции. А поскольку для этой цели использовались служебные целые переменные ?A, ?B, ?С,…?O (всего 15 штук для максимально возможной размерности массива 15) выдача выглядела странно и во многом теряла ценность:
X(?A,?B)=    1 X(?A,?B)=    2 X(?A,?B)=    3 X(?A,?B)=    4 X(?A,?B)=    5 
X(?A,?B)=    6 X(?A,?B)=    7 X(?A,?B)=    8 X(?A,?B)=    9
            Для исправления этого недостатка потребовалось доработать системную библиотеку. В случае оператора put data с индексами компилятор не просто формирует текстовую константу — имя переменной с подставленными индексами ?A, ?B, ?С,…?O, но и дописывает перед каждым индексом специальный не выводимый символ 07FH. При выполнении программы служебная процедура чтения очередного байта внутри системного вызова, с помощью которого обеспечивается весь вывод, обнаружив этот символ, в свою очередь, превращается в конечный автомат, который распознает идущую следом букву и находит текущее значение соответствующей переменной. Затем он переводит это значение в текст и посимвольно начинает выдавать этот текст вместо служебного символа и названия индекса внутри текстовой строки. В результате, наконец, получается правильное перечисление всех индексов элементов массива.

Переменные с именами при вводе

Естественно, имеется парный оператор к put data — это get data, который должен вводить значения переменных, заданные вместе с их именами. Но здесь, на мой взгляд, разработчики PL/1 переусложнили данный оператор. В соответствии со стандартом языка [2] оператор ввода должен не просто пропускать имена, но распознавать их и записывать следующее далее значение по нужному адресу. Например, если перетасовать значения, выданные put data(x), в другом порядке:
X(1,2)=    2 X(1,1)=    1 X(2,3)=    6 X(2,1)=    4 X(2,2)=    5 
X(3,1)=    7 X(1,3)=    3 X(3,2)=    8 X(3,3)=    9
То оператор get data(x) все равно должен ввести значения в правильном порядке, распознав каждое имя и индексы перед знаком равенства. И хотя такая обработка дает дополнительные удобства, например, можно пропускать данные, которые не меняются при вводе, все-таки это слишком хлопотно и сложно для реализации. А, главное, для такой возможности нужно иметь таблицу имен переменных и их адресов в каждом exe-файле, использующем get data. Поэтому я не стал реализовывать распознавание имен при вводе, а ограничился при чтении простым пропуском всех символов до знака равенства при разборе очередного элемента. Самое важное для меня — операторы put data и get data остались «зеркальными» и выдача данных оператором put позволяет потом безошибочно читать их оператором get с таким же списком объектов.

Заключение

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

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

Литература

1. Караваев Д.Ю. К вопросу о совершенствовании языка программирования. RSDN Magazine #4, 2011
2. American National Standard: programming language PL/I. New York, NY, American National Standards Institute, 1979 (Rev 1998); 403p. ANSI Standard X3.53-1976.

Автор: Д.Ю.Караваев. 26.03.2015

Последняя правка: 2018-11-01    14:33

Оцените

Отзывы

     2018/11/01 18:36, Автор сайта

В LISP, PHP, Clipper есть функция EVAL, которая компилирует «на лету». Поэтому в этих языках без проблем можно сделать что-то подобное put data и get data. Это ж языки с динамической типизацией, это им свойственно. В языках со статической типизацией сложнее. В Си выходят из положения с помощью макросов. Например:
#define  fl       printf("%s: %d ", __FILE__, __LINE__)
#define tx(arg) pr("%s", arg)
#define di(arg) pr("%d", arg)
#define d(arg {fl; tx(#arg); tx(" = <"); di(arg); tx(">\n");}
После этого в программе можно выводить значение переменных с именами:
int  value = 99;
d(value);
Будет выведено:
myprog.c: 195 value = 99
Но макросы — это «бестиповая» часть языка, которая, как многие справедливо считают, пытается скрыть недостатки языка. «Хороший язык в макросах не нуждается».

Но вот get data в Си невозможен: чтобы так, налету, зная имя только переменной (которое «исчезает» после компиляции), поменять его значение... Нет! Но можно было бы подумать над реализацией такого:
Вывод и ввод ("Введите значение переменной", value, ввод значения ());
На экране:
Введите значение переменной value _

     2018/11/02 08:48, kt

На мой взгляд, для отладки возможности одного только оператора # в макросах маловато. Тут действительно лучше бы возможности Лиспа. Например, вот типичная отладочная печать:
PUT SKIP DATA(X, Y, X/2+Y/2);
И в протоколе я получаю сразу нормальную расшифровку:
X=  1.345000E+01 Y=  4.472382E+03 X/2+Y/2=  2.242916E+03
А как это макросами сделать, не представляю…

     2018/11/02 15:24, Comdiv

Макросом с переменным количеством аргументов и проверкой типа с помощью _Generic из ISO C11 или с помощью __builtin_types_compatible_p из GNU C

     2018/11/02 15:42, kt

Это ответ на мой вопрос или на задачку автора сайта? Если на мой — то реализуйте отладочный вывод put data(x,y, x/2+y/2); как пример на чем-нибудь Си-образном.

     2018/11/02 16:41, Comdiv

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

     2018/11/02 20:23, Автор сайта

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

Но в C++ его можно заменить полиморфным оператором «<<»:
cout << [много, что можно вывести]
Заменив printf на «<<», печатаем и целые, и плавающие числа, и строки... Тогда, вооружившись модернизированным макросом и разбив (X, Y, X/2+Y/2) на части
d(X);
d(Y);
d(X/2+Y/2);
получим:
myprog.c: 195 X = 0.2
myprog.c: 196 Y = 0.6
myprog.c: 197 X/2+Y/2 = 0.4
Значения переменных, как видно, выводятся по одному. Можно, конечно, написать макрос d3 для трёх аргументов, чтобы вывести то же, что и Дмитрий Юрьевич. Но фокус его примера не в том, что аргументров три, а в том, что их число переменно. В привёденные макросы уступают средствам PL/1. Но в этих макросах есть удобная вещь: можно, как вы заметили, вывести имя файла-исходника и номер строки в нём. Интересно как это делается в PL/1?

И ещё: в С++ есть полимофный оператор ввода «>>»:
cin >> value;
Так что вывести имя переменной, а потом ввести её значение вполне возможно.

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

     2018/11/03 13:27, kt

Так в первой статье были перечислены константы ПЛ/1. Они обычные, такие, как и у всех:
Используются следующие константы:
  • ?DATE — дата сборки программы как строка из 8 символов.
  • ?MD — имя модуля программы
  • ?FILE — имя файла с исходным текстом программы
  • ?PROC — имя текущей процедуры
  • ?LINE — номер текущей строки исходного текста
  • ?INCL — имя текущего файла оператора %INCLUDE
  • ?VER — номер текущей версии компилятора
Но главное, на мой взгляд, в другом: PUT DATA не некий фокус в языке, а повседневный, постоянно используемый инструмент при отладке. Тот факт, что во многих языках такое надо делать костылями, да еще сразу и не сообразишь как, показывает невнимание к вопросам отладки. Никакого волшебного преимущества этот оператор не дает, но время и внимание экономит.

     2018/11/03 14:33, Автор сайта

Моё знакомство с PL/1 закончилось очень давно, поэтому простите моё незнание и наивные вопросы. Является какое-либо средство языка фокусом или нет, зависит от возможности повторить что-то иначе, пользуясь только средствами самого языка, не залезая в компилятор. Хороший язык программирования должен давать широкие возможности без фокусов. В том числе и в отладке.

     2018/11/03 17:30, kt

Да ничего такого наивного нет. Вопрос об отладке не совсем простой. На мой взгляд, некоторые стороны отладки вообще находятся принципиально "вне" языка.

     2018/11/05 14:02, Comdiv

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

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

Эта одна из проблем в языкостроении. Я её называю проблемой восприятия маленьких примеров, когда рассматривается небольшой код и делается вывод без попыток оценки влияния на большой код.

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

     2018/11/05 16:56, kt

Честно говоря, не понял, причем здесь маленькие или немаленькие примеры. Речь идет о поддержке со стороны языка некоторых отладочных действий. 55 лет назад разработчики PL/1 придумали следующие элементы поддержки отладки:

1. Опция «copy» в операторах чтения, она копировала входной поток в стандартный вывод при чтении. В расчетных задачах со многими входными параметрами получалась их автоматическая распечатка. В нашем компиляторе ее нет, но и работа с файлами со времен IBM/360 упростилась.

2. Опция SNAP в обработчике описаний.

3. Отслеживание состояний переменных с помощью состояния «CHECK».
Этих двух возможностей в нашем компиляторе также нет, но есть более широкие возможности за счет встроенного в каждый EXE маленького интерактивного отладчика. Одна из самых используемых его возможностей — аппаратная контрольная точка с проверкой состояния, например, директива K X1 W1 #0 означает установить аппаратную точку на запись в байтовую переменную X1 пока она станет не равной нулю, т.е. записи нулей отладчик пропускает, а на записи не нуля останавливается и можно посмотреть, где в программе это случилось.

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

Все эти действия не заменяют и не отменяют отладку, а лишь автоматизируют некоторые частые рутинные операции. Развитая IDE может предоставить также широкие возможности, но зато здесь возможен и поиск ошибок, так сказать, в полевых условиях, когда доступен только EXE-файл, без IDE. Выдача значений переменных с помощью отладчика и PUT DATA дополняют друг друга.

     2018/11/05 18:11, Comdiv

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

Я привёл статистику из своего кода, сколько раз потребовалось продублировать имена переменных. Если рассматривать эти два "маленьких примера" отдельно от всего кода, то PUT DATA может выглядеть как полезная возможность, если же учитывать весь объём кода, то его польза умещается в 0.05% — т.е. статистическую погрешность. В таком случае это просто захламляющая язык возможность. Надеюсь, я прояснил, что означает проблема восприятия маленьких примеров.

При этом я не отрицаю наличия языков или подходов, где такая возможность может быть востребована и полезна в языке. Например, Вы могли бы сказать, что у Вас она присутствует в 1 — 10% строк кода и серьёзно экономит Ваше время. Я лишь хотел прояснить, как так могло получиться, что в других языках "надо делать костылями, да еще сразу и не сообразишь как". Здесь ключевое слово — "надо" (ли).

     2018/11/05 19:04, Автор сайта

Позвольте мои 5 копеек вставить. Есть такая задача — написание компилятора. Не скажу, что это распространённая задача, но тем не менее. Как правило, заказчики просят покрыть все аспекты языка тестами. Одно дело — в отладчике глазами проверять работу компилятора. Другое дело — задокументировать прогон тестов и выдать это заказчику. Думаю, существуют и другие варианты, когда требуется что-то подобное PUT DATA. У Вас, может быть, 0.05%, а у кого-то поболее. По всякому бывает.

     2018/11/05 19:26, kt

По-моему, мы имеем ввиду разные вещи. Отладочная печать используется в процессе отладки или поиска ошибки. После окончания отладки или понимания ошибки чаще всего отладочную печать удаляют или комментируют, чтобы не засорять протоколы. Поэтому в «конечном» исходном тексте может вообще не быть никаких PUT DATA, а в процессе работы с программой их может быть много. Процент в строках здесь не показатель.

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

     2018/11/05 19:28, Comdiv

Забавно, но я как раз на примере транслятора приводил. А так — я и не отрицаю возможность задач, в которых нечто подобное было бы полезно. Я как раз возражаю против подхода — "я так пишу, а у других этого нет, значит они не доработали" или наоборот — "мне это не надо, значит никому не надо".
Это ещё одна проблема в восприятии других решений. Все решают разные задачи с разными подходами, но каждый смотрит, в первую очередь, со своей точки зрения и в результате — сплошное недопонимание.

     2018/11/05 19:31, Comdiv

Поэтому в «конечном» исходном тексте может вообще не быть никаких PUT DATA, а в процессе работы с программой их может быть много. Процент в строках здесь не показатель.

Когда-нибудь я, действительно, удалю отладочную печать, но пока всё, что было — остаётся. Отключаю я её настройками, а не удалением.

     2018/11/05 20:14, kt

Конечно все решают разные задачи с разными подходами, стилями и даже убеждениями. Не об этом речь. Я, как и другие ПЛ-программисты, активно использую небольшую полезность языка, специально предназначенную для целей отладки. А в Вашей системе программирования есть ли какая-то поддержка отладки на уровне языка? Я упоминаю PUT DATA не потому, что именно я так делаю, а потому, что это есть в языке и понятно зачем.

Автору же сайта могу сообщить что в моем случае с тестами он попал в точку, вот фрагмент теста работы с комплексными числами:
PUT SKIP LIST('-'(15),'ПРИСВАИВАНИЯ НАЧАЛЬНЫХ ЗНАЧЕНИЙ','-'(15));
X1='1+2I';
X2='3+4i';
X3=5I;
PUT SKIP DATA(X1,X2);
PUT SKIP DATA(X3);
PUT SKIP DATA(X4);
SSS='5I';
X3=SSS;
PUT SKIP DATA(X3);
Y1='1+2I';
Y2='3+4i';
Y3=5I;
PUT SKIP DATA(Y1,Y2);
PUT SKIP DATA(Y3);

     2018/11/05 21:18, Comdiv

Да, есть встроенное средство отладки — это ASSERT. Если повезёт, сработает ещё до запуска. Негусто?

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

Когда нужно отфильтровать отладочный вывод по файлу/функции/другому_признаку, что делают ПЛ-программисты?

     2018/11/05 21:40, kt

Обычно пишут в операторах печати константы ?FILE и ?PROC, а затем засовывают протокол в Excel и устанавливают фильтр ))

     2018/11/05 21:51, Comdiv

А нельзя обойтись средствами самой программы?

     2018/11/11 12:57, Александр Коновалов aka Маздайщик

Однако в языке PL/1 есть хорошая основа для доработок в виде возможности записать оператор цикла прямо внутри оператора ввода-вывода. В свое время разработчикам языка вывод массивов казался очень важным. Поэтому они разрешили писать цикл ввода-вывода в виде:

put list((x(i) do i=lbound(x) to hbound(x)));

Английская Википедия пишет [https://en.wikipedia.org/wiki/List_comprehension], что списковые включения появились в языке SETL в 1969 году. Но язык PL/I, появившийся в 1964 году, уже содержал ограниченный вариант списковых включений.

А на счёт put data — это хороший инструмент для отладочной печати.

Написать отзыв

Написать автору можно на электронную почту mail(аt)compiler.su

Авторизация

Регистрация

Выслать пароль

Карта сайта


Каким должен быть язык программирования?

Анализ и критика

Описание языка

Компилятор

Отечественные разработки

Cтатьи на компьютерные темы

Двадцать тысяч строк кода, которые потрясут мир?

Почему владение/заимствование в Rust такое сложное?

Масштабируемые архитектуры программ

Почему Хаскелл так мало используется в отрасли?

Бесплатный софт в мышеловке

Исповедь правового нигилиста

Русской операционной системой должна стать ReactOS

Почему обречён язык Форт

Программирование без программистов — это медицина без врачей

Электроника без электронщиков

Статьи Дмитрия Караваева

●  Идеальный транслятор

●  К вопросу о совершенствовании языка программирования

●  О реализации метода оптимизации при компиляции

●  О реализации метода распределения регистров при компиляции

●  О распределении памяти при выполнении теста Кнута

●  Опыты со стеком или «чемпионат по выполнению теста Кнута»

●  Сколько проходов должно быть у транслятора?

●  Чтение лексем

●  Экстракоды при синтезе программ

●  Об исключенных командах или за что «списали» инструкцию INTO?

●  Типы в инженерных задачах

●  Непрерывное компилирование

●  Об одной реализации специализированных операторов ввода-вывода

●  Особенности реализации структурной обработки исключений в Win64

●  О русском языке в программировании

●  Формула расчета точности для умножения

●  Права доступа к переменным

●  Скорость в попугаях

●  Крах операции «Инкогнито»

●  Предопределенный результат

Компьютерный юмор

Прочее

Последние комментарии

2018/11/20 20:13 ••• ivan.ee
✎ Программирование без программистов — это медицина без врачей

2018/11/18 17:14 ••• kt
✎ Экстракоды при синтезе программ

2018/11/18 15:26 ••• Freeman
✎ Так ли нужны операции «&&», «||» и «^^»?

2018/11/18 15:21 ••• Freeman
✎ Устарел ли текст как форма представления программы

2018/11/17 03:28 ••• Comdiv
✎ Изменение длины объекта в стеке во время исполнения

2018/11/16 12:53 ••• Автор сайта
✎ Помеченные комментарии

2018/11/11 14:01 ••• Александр Коновалов aka Маздайщик
✎ Нерабочий код

2018/11/11 13:39 ••• Александр Коновалов aka Маздайщик
✎ О русском языке в программировании

2018/11/11 12:57 ••• Александр Коновалов aka Маздайщик
✎ Об одной реализации специализированных операторов ввода-вывода

2018/11/03 22:43 ••• rst256
✎ Непрерывное компилирование

2018/11/02 23:23 ••• Неслучайный читатель
✎ Сколько проходов должно быть у транслятора?

2018/11/01 19:10 ••• Автор сайта
✎ Об исключенных командах или за что «списали» инструкцию INTO?