Inferno OS Wiki
Advertisement

(limbo.pdf)

9.15. Опреатор raise

Оператор raise имеет следующий вид:

raise expressionopt ;

и вызывает исключение в процессе. expression1 может быть как строкой описывающей сбой, так и именем исключения и передаваемыми параметрами если такие имеются. Если выражение не указано, оператор raise должен быть в теле обработчика исключений; он вызывается при активном на данный момент исключении.

9.16. Обработчик исключений

Существует целый класс различных ошибок в программе Limbo которые могут быть обнаружены только во время выполнения. Они включают в себя ошибки программирования, такие как выход за границы массива, системные ошибки, как например, исчерпание памяти и исключения задаваемые пользователем во время компиляции при помощи объявления исключений и последующим их вызовом оператором raise во время выполнения. Группа операторов может иметь связанный с ними обработчик исключений:

{ statements } exception identifieropt{ qual-statement-sequence }

Первое исключение времени выполнения вызванное любым из операторов в statements1, или в функциях которые они вызывают, которые перехватывается обработчиком исключений вызовет прерывание выполнения в этой точке и передаст управление в последовательность уточняющих квалификторов соответствующих исключению. Исключение представлено строкой соответствующей квалификатору и может являться как точно такой же строкой, или префиксом с последующим символом * (шаблоном).

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

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

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


(addenium.pdf)

5. Исключения

Теперь предусмотрены исключения, определяемые пользователем, и строковые исключения. Интерфейс в модуле Sys, предоставлявший доступ к исключениям, был удален и заменен новой языковой конструкцией в Limbo.

5.1. Строковые исключения

Простые строковые исключения могут быть вызваны следующим образом

raise s;

где s - любое значение типа string (которое не обязательно должно являться константой).

Обработчики исключений могут быть присоединены к блоку команд (или последовательности выражений):

{
	foo();
	bar();
} exception e {
"a" or "b" =>
	sys->print("caught %s\n", e);
	raise;
"ab*" =>
	sys->print("caught %s\n", e);
	exit;
"abcd*" =>
	sys->print("caught %s\n", e);
	raise e;
"a*" =>
	sys->print("caught %s\n", e);
	raise "c";
"*" =>
	sys->print("caught %s\n", e);
}
LL:

Любое исключение возникающее внутри блока (и во вложенных вывзовах функций внутри блока) потециально могут быть перехвачены обработчиком исключений. Исключение перехватывается при помощи точного сопадения охранного выражения (квалификатора) или охранного шаблона "s*" , где s является префиксом строки строки исключения. Используется наиболее подходящее соответствие. Таким образом, вызов "a" будет перехвачен первым охранным выражением, а не вторым или четвертым. Если соответствие найдено, выполняется последовательность операторов следующая за охранным выражением. В случае, если соответствие не найдено, система ищет обработчик верхнего уровня.

Как было показано выше, исключение доступно через идентификатор исключения (в нашем случае это e) если он следует за ключевым словом exception.

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

raise;
or
raise e;

И блок команд и код внутри exception пройдет через оператор с меткой LL если только, конечно, они не завершаться аварийно, не закончат программу по exit, или return или не вызовут другое исключение.

5.2. Исключения, определяемые пользователем

Вы можете объявить свои собственные исключения:

implement Fibonacci;

include "sys.m";
include "draw.m";

Fibonacci: module
{
	init: fn(nil: ref Draw->Context, argv: list of string);
};

init(nil: ref Draw->Context, nil: list of string)
{
	sys := load Sys Sys->PATH;
	for(i := 0; ; i++){
		f := fibonacci(i);
		if(f < 0)
			break;
		sys->print("F(%d) = %d\n", i, f);
	}
}

FIB: exception(int, int);

fibonacci(n: int): int
{
	{
		fib(1, n, 1, 1);
	}exception e{
	FIB =>
		(x, nil) := e;
		return x;
	"*" =>
		sys->print("unexpected string exception %s raised\n", e);
	* =>
		sys->print("unexpected exception raised\n");
	}
	return 0;
}

fib(n: int, m: int, x: int, y: int) raises (FIB)
{
	if(n >= m)
		raise FIB(x, y);
	{
		fib(n+1, m, x, y);
	}exception e{
	FIB =>
		(x, y) = e;
		x = x+y;
		y = x-y;
		raise FIB(x, y);
	}
}

FIB объявляет исключение которое возвращает два целых числа. Значения подставляются когда вызывается исключение:

raise FIB(3, 4);

Затем исключение перехватывается и значения могут быть восстановлены путем рассмотрения объявленного идентификатора исключения как если бы это был тьюпл из двух целых чисел:

(x, y) = e;

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

Если вы проделаете

"abcde" or FIB =>
	(x, y) = e;
	sys->print("%s\n", e);

вы получите ошибку компилятора как неопределенный тип e этой альтернативы.

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

Обратите внимание на разницу в охранных строках строках "*" и * в функции fibonacci. Шаблон будет соответсnвовать любому строковому исключению, а последнее выражение ЛЮБОМУ исключению. Если возникшее строковое исключение соответствует шаблону, тогда выбирается он, как более точный. Если появляется неопределенное пользователем исключение, оно будет соответствовать второму варианту.

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


5.3. Условие raises

Объявление функции fib в выше приведенном примере также перечисляет определяемы пользователем исключения, которые могут вызываться посредством использования условия raises. В этом случае, тут есть только одно искючение (FIB). Эти условия (если они даны) должны быть совместимы любым объявлением и определением функции.

Компилятор сообщает об экземлярах функций которые вызывают исключение которое не упоминается в условии raises или не вызывает некоторые исключения которые были упомянуты в условии raises. Сейчас эти сообщения носят характер предупреждений.


Письмо из рассылки

Автор: Charles Forsyth

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

Обычно, мы пользуемся именованными исключениями только внутри модуля (мы их обычно не экспортируем). Я имею склонность пользоваться ими только для обработки ошибок в рекусрсивном парсинге или подобных вещах.

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

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

Исклюения могут быть полезны даже во время вызова последовательности операций, где ошибка в одном приведет к появлению ошибки в остальных (и нам не важно какой был ошибочным):

	{
		parsethis();
		parsethat();
		parseotherthing();
		...
		manyotherthings();
	}exception{
	* =>
		return (nil, "syntax error");	# на практике, мы так же вернем смещение и контекст ошибочного  варианта
	}

аналигично и в расчетах

	{
		a := array[n] of int;
		for(i := 0; i readjson(fd);
# это не является ни ошибкой программирования, ни сбоем если  err != nil,
# и readjson не вызвал исключения. Это мошло бы в действительности, 
# представлять серьезную ошибку в большой системе, но я написал
		if(err != nil)
			raise "serious error in larger system: "+err;
# однако, я точно так же мог написать
		if(err != nil){
			moantouser(...);
			continue;
		}
# или записать лог в файл и выйти

Аналогично, если я запишу:

	kill(pid: int): int
	{
		fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
		if(fd == nil)
			return -1;
		return sys->fprint(fd, "kill");
	}

Тогда открытие файла может вернуть ошибку, покольку процесс уже не существует, и тогда это обычно не является важным; sys->fprint могло быть более серьезным (или не могло): все это зависит от условий на момент вызова.

Теперь я в контексте вызова, пишу

	(val, err) := json->readjson(fd);
	if(err != nil)
		...;

или

	if(kill(pid) readjson(fd);
	}exception e{
	DonovanMissing =>
		...
	* =>
		raise;
	}

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

Имеет ли значение то, что исключение произошло в библиотечном модуле? Я придерживаюсь совету из Erlang: да, если это обычный принцип "выхода за границы" ошибки программирования (или сбоя). У некоторых из Erlang'овских модулей есть две функции: одна возвращает nil (допустим), а другая вызывает исключение. Например, в модуле table есть "find" (который возвращает тьюпл) и "fetch" который возвращает обычное значение или вызывает исключение. Так, если вы наполнили таблицу значениями, и позже извлекаете их, вы знаете, что обязаны это сделать, вы напишите "fetch". Если этого не сделать, это будет ошибкой программирования, такой как использование нулевого указателя, и вы получите ожидаемое исключение.

Имеются также и коварные ловушки. Случай с Runeerror (который мне кажется где-то обсуждался) это один из них. Должны ли вы возвращать ошибку (или вызывать исключение) в случае неверной послетовательности байт для utf-8 символа? И Plan 9 и Inferno заменют последовательность на специальную utf-8 руну (со знаком вопроса).

Java так не поступает. Java никогда не гарантирует что вы получите конвертер UTF-8! В действительности это происходит, но компилятор не знает какой параметр вы используете, что я сам обнаружил написав:

	public static String S(byte[] a){
		try{
			return new String(a, "UTF-8");
		}catch(java.io.UnsupportedEncodingException e){
			return "Egosling";
		}
	}

и

	public static final byte[] S(String s){
		if(s == null)
			return new byte[0];
		try{
			return s.getBytes("UTF-8");
		}catch(java.io.UnsupportedEncodingException e){
			return "Egosling".getBytes();
		}
	}

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



Особенности именования исключений (by powerman-asdf)



. raise "interrupted"

  • особый случай, нежелательно использовать в своих приложениях
  • вроде бы используется во внутренних целях dis при убивании процесса kill-ом
  • broken-процесс не остаётся
  • sh обрабатывает это как обычный raise "anything", см. ниже


. raise "fail:anything" и raise "fail:"

  • "легальное" поведение приложения, broken-процесс не остаётся
  • sh не выводит никаких сообщений
  • sh выставляет $status в текст исключения без начального "fail:"
  • при raise "fail:" sh устанавливает $status в "failed"
  • рассматривается sh в логических конвейерах && || как ложь


. raise "anything"

  • аварийное поведение приложения, остаётся broken-процесс для отладки
  • sh выводит сообщение 'sh: PID "MODULENAME":anything'
  • sh выставляет $status в текст исключения
  • рассматривается sh в логических конвейерах && || как ложь
Advertisement