Небольшой язык программирования и его разработка

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

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Как всё началось

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

Переходя в следующий курс я начал активно изучать всё что касалось ОСи, но толком никуда не продвинулся. Тогда и родилась у меня идея создать свой ЯП.

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

Язык я решил назвать The Gorge.

Часть первая. Как работает язык и где его найти

Было принято решение разместить язык на платформе гитхаб и создать новый профиль.
На тот момент я имел в распоряжении старый подаренный мне акк, но в последствии всё-таки создал свой и сейчас его можно найти так: (просто допишите сайт)/pcPowerJG/natural-network.

В папке src в файле lib.rs мы можем увидеть чудо, язык написан почти полностью на раст (почему почти? к сожалению в далёкие времена 2019 года раст не давал открыть файл на моей любимой манжаре и пришлось открывать его через Си).

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

words.push("object".to_string());//1	 // используется для создания объекта, который хранит значения в памяти	
words.push("if".to_string());//2	// оператор условия, нужен для сравнения ДВУХ параметров		
words.push("exit_()".to_string());//3//выход из приложения
words.push("func".to_string());     //4//инициализация функции 
words.push("print".to_string());//5 // вывод на консоль		
words.push("remove".to_string());//6 //удаление
words.push("array".to_string());//7 // создание массива
words.push("struct".to_string()); //8 // создание структуры
words.push("end".to_string());//9//end operation
words.push("end_func".to_string()); // 10 // конец функции	
words.push("return".to_string()); //  11
words.push("!eq".to_string());//  12
words.push(">".to_string());  //  13
words.push("<".to_string());  //  14
words.push("loop".to_string());// 15
words.push("end_loop".to_string());// 16		
words.push("_".to_string()); // 17 // просто в качестве НЕ ключевого слова
words.push("break".to_string()); // 18
words.push("true".to_string()); // 19
words.push("false".to_string()); // 20

Как мы видим у слов есть определённая нумерация (и не с нуля. это важно).

Следующая главная функция это функция старт.

pub fn start_(&mut self, text: String) -> u8

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

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

Так же создадим переменные для временного хранения информации (тело цикла к примеру или математическое выражение, имя функции и т.д.).

Привожу код.

let mut temp_values: String = String::new();			//	ВРЕМЕННЫЕ ПЕРЕМЕННЫЕ
let mut temp_name: String = String::new();		        //	...
let mut temp_buffer: String = String::new();			//	...
let mut func_text: String = String::new();
let mut last_op: [usize; 3] = [0; 3]; // храним три последних действия
// ----------------------------------------------
let mut if_count: usize = 0;
let mut if_result: bool = true; // ответ на условие
let mut struct_flag: bool = false; // это структура или условие
let mut function_inactive_flag: bool = false; // если функция не активна
let mut loop_flag: bool = false; // попали на цикл
let mut index_loop: usize = 0; // количество циклов (для цикла в цикле)

Так же смотря код можно заметить много флагов, они нужны как-раз таки для нормального функционирования.

Всего наш код делиться на три блока

if ch == ' ' || ch == '\t' {
  //...................
} else if ch == '\n' {
  //...................
} else if ch == '=' {
  //...................
}
} else {
  temp_values.push(ch);
}

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

Дальше ныряем по коду, предлагаю посмотреть каждый блок отдельно.

if function_inactive_flag {
  // ...						
}
if loop_flag {
  // ...
}
match temp_values.trim() {
  // ...
}

Мы видим в блоке ещё три блока. Первый блок связан с функциями, в нём происходит запись их в переменную (мы храним имя функции как переменную).

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

Всё остальное же идёт согласно законам логики построения программы.

К примеру: a = b + c
Преобразуется в: last_op[0] = 1 last_op[1] = 17
И выполниться: в функции math_work .

Математика

Функция преобразования переменных в значения:

fn math_work(&self, text: String) -> String {
  let text: String = Words::trim(text.clone());
  let mut result_string: String = String::new();
  let mut temp_string: String = String::new();
  for ch in text.chars() {
    match ch {
      '+' | '-' | '/' | '*' | '(' | ')' | '&' | '|' | '!' | '=' | '<' | '>' => {
        if Words::is_digit(temp_string.clone()) {
          result_string += temp_string.clone().as_str();							
        } else {
          result_string += self.search_var(temp_string).0.clone().as_str();
        }
        result_string.push(ch.clone());
        temp_string = String::new();
      },
      _ => {
        temp_string.push(ch.clone());
      },
    }
  } 
  let (value, type_, _temp) = self.search_var(temp_string.clone());
  if _temp {
    result_string += value.as_str();
  } else {
    result_string += temp_string.clone().as_str();
  } result_string
}

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

Передаём всё в следующую функцию:

fn eval(str_: Vec<char>) -> f32 {
  let mut i: usize = 0;
  Words::expr(str_, &mut i)
}
Вся математическая магия
fn eval(str_: Vec<char>) -> f32 {
  let mut i: usize = 0;
  Words::expr(str_, &mut i)
}

fn plus_one(u: &mut usize) {
  *u += 1;
}

fn number(ch_: Vec<char>, idx: &mut usize) -> f32 {
  let mut result: f32 = 0.0;
  //float result = 0.0;
  let mut div: f32 = 10.0;
  let mut sign: f32 = 1.0;
  if ch_[*idx] == '-'{
    sign = -1.0;
    *idx += 1;
  }

  while *idx < ch_.len() &&
  match ch_[*idx] {
    '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => { true },
    _ => { false }
  }
  {
    result = result * 10.0 + (f32::from_str(&ch_[*idx].to_string()).expect("не удалось форматировать строку"));

    *idx += 1;
  }

  if *idx < ch_.len() && (ch_[*idx] == '.'){
    *idx += 1;        
    while *idx < ch_.len() &&
    match ch_[*idx] {
      '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => { true },
      _ => { false }
    } 
    {
      result = result + (f32::from_str(&ch_[*idx].to_string()).expect("не удалось форматировать строку")) / div;
      div *= 10.0;
      *idx += 1;
    }
  }
  sign * result
}

fn expr(ch_: Vec<char>, idx: &mut usize) -> f32 {
  let mut result: f32 = Words::term(ch_.clone(), idx);    
  while *idx < ch_.len() && (ch_[*idx] == '+' || ch_[*idx] == '-') {
    match ch_[*idx] {
      '+' => {
        *idx += 1;
        result += Words::term(ch_.clone(), idx);
      },
      '-' => {
        *idx += 1;    
        result -= Words::term(ch_.clone(), idx);
      },
      _ => {},
    } 
  } result
}

fn term(ch_: Vec<char>, idx: &mut usize) -> f32 {
  let mut result: f32 = Words::factor(ch_.clone(), idx);
  let mut div: f32 = 0.0;

  while *idx < ch_.len() && (ch_[*idx] == '*' || ch_[*idx] == '/') {
    match ch_[*idx] {
      '*' => {
        *idx += 1;
        result *= Words::factor(ch_.clone(), idx);
      },
      '/' => {
        *idx += 1;    
        div = Words::factor(ch_.clone(), idx);    
        if (div != 0.0) {
          result /= div;
        } else {
          panic!("Division by zero!\n");                    
        }
      },
      _ => {},
    }
  } result
}

fn factor(ch_: Vec<char>, idx: &mut usize) -> f32 {
  let mut result: f32 = 0.0;
  let mut sign: f32 = 1.0;

  if (ch_[*idx] == '-') {
    sign = -1.0;
    *idx += 1;
  }

  if (ch_[*idx] == '(') {
    *idx += 1;
    result = Words::expr(ch_.clone(), idx);

    if (ch_[*idx] != ')') {
      panic!("Brackets unbalanced!\n");
    }
    *idx += 1;
  } else { result = Words::number(ch_, idx); }
  sign * result
}

Переменные

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

Все переменные занимают место сразу в двух массивах, массив имён и типов:

object_buffer: Vec<(String, usize)>

Массив значений:

value_buffer: Vec<String>

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

fn add_vars(&mut self, vars_name: String, mut vars_value: String, vars_type: usize) {
  //object_buffer: Vec<(String, usize)>
  //value_buffer: Vec<String>
  if vars_value.clone().split('\"').collect::<Vec<&str>>().len() > 1 {
    vars_value = vars_value.split('\"').collect::<Vec<&str>>()[1].to_string();
  } else {
    vars_value = vars_value.clone().trim().to_string();
  }
  self.object_buffer.push((vars_name, vars_type));
  self.value_buffer.push(vars_value);
}

В ней всего одна проверка, есть ли кавычки (что в кавычках мы считаем текстом и не отрезаем пробелы).

Для удаления переменной:

fn remove_vars(&mut self, vars_name: String) {
  for i in 0..self.object_buffer.len() {
    if self.object_buffer[i].0.clone() == vars_name {
      self.object_buffer.remove(i);
      self.value_buffer.remove(i);
      return;
    }
  }
}

Запись значения и поиск:

fn set_value(&mut self, vars_name: String, mut vars_value: String) {
  for i in 0..self.object_buffer.len() {
    if self.object_buffer[i].0 == vars_name {
      if vars_value.clone().split('\"').collect::<Vec<&str>>().len() > 1 {
        vars_value = vars_value.split('\"').collect::<Vec<&str>>()[1].to_string();
      } else {
        vars_value = vars_value.clone().trim().to_string();
      }
      self.value_buffer[i] = vars_value.clone();
      return;
    }
  }

}
pub fn search_var(&self, vars_name: String) -> (String, usize, bool) {
  for i in 0..self.object_buffer.len() {
    if self.object_buffer[i].0 == vars_name {
      let value: String = self.value_buffer[i].clone();
      let type_: usize = self.object_buffer[i].1.clone();
      return (value, type_, true); 
    }
  }
  (String::new(), 0, false)
}

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

import("/lib.so")
	extern_func("lib.so", func_name)
  extern_func("lib.so", func_name, arg1, arg2)
close_import("lib.so")

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

Что нужно исправить и добавить?

  1. Исправить функцию обработки условий (не заходите туда, я серьезно);

  2. Зачем нам всё хранить в тексте? Исправить на байты;

  3. Импортировать поддержу сишных библиотек из старой версии языка

Спасибо за внимание.

Источник: https://habr.com/ru/post/557674/


Интересные статьи

Интересные статьи

Эта статья — продолжение беседы с Максимом Барышниковым, Wargaming, Head of Platform. Первая часть, посвященная технологиям и архитектуре, уже была опубликована на Хабре. Эта часть посвящена упра...
Мы строили-строили, и наконец построили: расписание Moscow Python Conf++ собрано, проверено, перепроверено и опубликовано. Не то чтобы работа Программного комитета на этом заканчивалась (за два-т...
Всем привет! Несколько месяцев назад мы запустили в продакшн наш новый open-source проект — Grafana-плагин для мониторинга kubernetes, который назвали DevOpsProdigy KubeGraf. Исходный код плагина...
В 1С Битрикс есть специальные сущности под названием “Информационные блоки, сокращенно (инфоблоки)“, я думаю каждый с ними знаком, но не каждый понимает, что это такое и для чего они нужны
Хочу поделиться процессом разработки простой мобильной игры силами двух разработчиков и художника. Данная статья в большей мере состоит описания технической реализации. Осторожно, много текс...