Автоматическая вставка точки с запятой в Go
Формальная грамматика определяет, что составляет синтаксически допустимую программу на Go (или другом языке программирования):
Block = "{" StatementList "}" . StatementList = { Statement ";" } .
Приведенные выше определения взяты из спецификации Go. Они используют расширенную форму Бэкуса-Наура (EBNF). Все это означает, что блок кода - это одно или несколько операторов, разделенных точкой с запятой. Вызов функции является примером оператора. Зная, что мы можем создать простой блок:
{ fmt.Println(1); fmt.Println(2); }
Опытные суслики должны заметить точки с запятой, которые не используются в конце каждой строки в идиоматическом коде. Его можно упростить до:
{ fmt.Println(1) fmt.Println(2) }
Такой код работает так же, как и первый. Но что делает это возможным, если грамматика требует точки с запятой?
Корни
Почему разработчики языков даже начали работать над избавлением от токенов, таких как точки с запятой? Ответ довольно прост. Все дело в удобочитаемости. Чем меньше в коде артефактов, тем проще с ним работать. Это важно, поскольку однажды написанный фрагмент кода, вероятно, будет много раз прочитан разными людьми.
Грамматика использует точки с запятой как терминаторы продукции. Поскольку цель состоит в том, чтобы освободить программиста от ввода этих точек с запятой, должен быть способ их автоматического ввода. Это то, что делает лексер Go. Точка с запятой добавляется, когда последний токен строки является одним из:
- идентификатор
- целочисленный, с плавающей точкой, воображаемый, рунический или строковый литералы
- одно из ключевых слов
break
,continue
,fallthrough
илиreturn
- один из операторов и разделителей
++
,--
,)
,]
или}
Приведем пример:
func g() int { return 1 } func f() func(int) { return func(n int) { fmt.Println("Inner func called") } }
Имея такие определения, мы можем проанализировать два сценария:
f() (g())
а также:
f()(g())
Первый фрагмент ничего не печатает, а второй даетInner func called
. Это из-за 4-го вышеупомянутого правила - точки с запятой были добавлены после обеих строк, поскольку последние токены закрывают круглые скобки:
f(); (g());
Под капотом
Добавление точек с запятой в Golang происходит при лексическом анализе (сканировании). Это в самом начале обработки файла .go, когда символы преобразуются в токены, такие как идентификаторы, числа, ключевые слова и т. Д. Сканер реализован в самом Go, поэтому мы можем легко его использовать:
package main import ( "fmt" "go/scanner" "go/token" ) func main() { scanner := scanner.Scanner{} source := []byte("n := 1\nfmt.Println(n)") errorHandler := func(_ token.Position, msg string) { fmt.Printf("error handler called: %s\n", msg) } fset := token.NewFileSet() file := fset.AddFile("", fset.Base(), len(source)) scanner.Init(file, source, errorHandler, 0) for { position, tok, literal := scanner.Scan() fmt.Printf("%d: %s", position, tok) if literal != ""{ fmt.Printf(" %q", literal) } fmt.Println() if tok == token.EOF { break } } }
Выход:
1: IDENT "n" 3: := 6: INT "1" 7: ; "\n" 8: IDENT "fmt" 11: . 12: IDENT "Println" 19: ( 20: IDENT "n" 21: ) 22: ; "\n" 22: EOF
Строки печати ; "\n"
- это места, где сканер (лексер) добавляет точки с запятой для программы:
n := 1 fmt.Println(n)
Golangspec насчитывает более 300 последователей. Это не было самоцелью, но все же очень воодушевляет знать, что все больше и больше людей видят эту публикацию полезной.
Нажмите ❤ ниже, чтобы помочь другим узнать эту историю. Если вы хотите получать обновления о новых сообщениях, подписывайтесь на меня.