Как реализован форматтер кода в Turtle Graphics

Как реализован форматтер кода в Turtle Graphics

4 ноября 2022 г.

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

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

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

n Давайте начнем наш рассказ с вашего файла, который содержит простой пример hello world n

fun main() {
   print("Hello, World!")
}

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

data class Token (
   val kind : TokenKind,
   val literal : String,
   val line : Int,
)

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

Error in File Main Line 10: Missing semicolon :D

Этот шаг называется сканером, лексером или токенизатором, и в конце мы получим список токенов, например, n

{ FUN_KEYWORD, "fun", 1 }
{ IDENTIFIER, "main", 1 }
{ LEFT_PAREN, "(", 1 }
{ RRIGHT_PAREN, ")", 1 }
{ LEFT_BRACE, "{", 1 }
{ IDENTIFIER, "print", 2 }
{ LEFT_PAREN, "(", 2 }
{ STRING, "Hello, World!", 2 }
{ RRIGHT_PAREN, ")", 2 }
{ RIGHT_BRACE, "}", 3 }

Результатом является список токенов n

val tokens : List<Token> = tokenizer(input)

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

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

n На этом шаге нам нужна структура данных для представления программы таким образом, чтобы мы могли пройти и проверить ее позже, и она называется абстрактным синтаксическим деревом (AST). Каждый узел в AST представляет такие операторы, как If, While, Объявление функции, объявление var и т. д. или выражения, такие как присваивания, унарные и т. д., каждый узел хранит необходимую информацию, чтобы использовать ее позже, например, на следующих шагах

n Объявление функции n

data class Function (
   var name : String,
   var arguments : List<Argument>,
   var body : List<Statement>
)

Объявление переменной n

data class Var (
   var name : String
   var value : Expression
)

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

var astNode = parse(tokens)

Если язык имеет статические типы, такие как Java, C, Go и т. д., мы перейдем к шагу проверки типов, целью которого является проверка того, что пользователь правильно использует тип, например, если пользователь объявляет переменную с типом int. должен хранить только целые числа, условие if должно быть логическим типом или целым числом на языке, подобном C … и т. д.

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

n Например, предположим, что мы хотим, чтобы все разработчики объявляли переменные без использования _ внутри имени, чтобы убедиться, что мы пройдем через наш узел AST, чтобы найти все узлы Var и проверить их n

fun checkVarDeclaration(node : Var) {
   if (node.name.contains("_") {
      reportError("Ops your variable name ${node.name} contains _")
   }
}

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

fun formatVarDeclaration(node : Var) : String {
   var builder = StringBuilder()
   builder.append(indentation)
   builder.append("var ")
   builder.append(node.name)
   builder.append(" = ")
   builder.append(formatValue(node.value))
   builder.append("n")   
   return builder.toString()
}

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

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

n Это базовая реализация средства форматирования кода, реальное средство форматирования производственного кода должно обрабатывать больше случаев, например, что, если код недействителен? Должен ли я форматировать только допустимый код? должны ли мы читать всю программу каждый раз, когда хотим отформатировать или скомпилировать код?

n Теперь вернемся к графике черепах. В этом проекте я уже сделал все необходимые шаги и получил готовый AST, поэтому я просто переписываю его с кодом, как вы видели выше ^_^ я прочитал его из формата пользовательского интерфейса и записать это обратно в пользовательский интерфейс в моем случае

n Если вам интересно и вы хотите узнать больше, я предлагаю

  • Прочитайте хотя бы одну книгу по компиляторам, например "Создание интерпретаторов".
  • Подробнее о протоколе языкового сервера (LSP)
  • Посмотрите объяснение компилятора Typescript от автора Андерса Хейлсберга
  • Подумайте, если у вас есть Ваша программа как AST, что еще вы можете с ней сделать

Надеюсь, вам понравилась моя статья, и вы можете найти меня на

Наслаждайтесь программированием 😋.


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE