ether-code 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/cli/ether.js +1 -1
  2. package/generators/css-generator.js +42 -55
  3. package/generators/graphql-generator.js +19 -22
  4. package/generators/html-generator.js +51 -220
  5. package/generators/js-generator.js +76 -157
  6. package/generators/node-generator.js +49 -93
  7. package/generators/php-generator.js +46 -68
  8. package/generators/python-generator.js +35 -54
  9. package/generators/react-generator.js +37 -47
  10. package/generators/ruby-generator.js +59 -119
  11. package/generators/sql-generator.js +42 -63
  12. package/generators/ts-generator.js +59 -133
  13. package/i18n/i18n-css.json +147 -147
  14. package/i18n/i18n-graphql.json +6 -6
  15. package/i18n/i18n-html.json +135 -135
  16. package/i18n/i18n-js.json +107 -107
  17. package/i18n/i18n-node.json +14 -14
  18. package/i18n/i18n-php.json +177 -177
  19. package/i18n/i18n-python.json +16 -16
  20. package/i18n/i18n-react.json +97 -97
  21. package/i18n/i18n-ruby.json +22 -22
  22. package/i18n/i18n-sql.json +153 -153
  23. package/i18n/i18n-ts.json +10 -10
  24. package/lexer/ether-lexer.js +175 -34
  25. package/lexer/tokens.js +6 -6
  26. package/package.json +1 -1
  27. package/parsers/ast-css.js +0 -545
  28. package/parsers/ast-graphql.js +0 -424
  29. package/parsers/ast-html.js +0 -886
  30. package/parsers/ast-js.js +0 -750
  31. package/parsers/ast-node.js +0 -2440
  32. package/parsers/ast-php.js +0 -957
  33. package/parsers/ast-react.js +0 -580
  34. package/parsers/ast-ruby.js +0 -895
  35. package/parsers/ast-ts.js +0 -1352
  36. package/parsers/css-parser.js +0 -1981
  37. package/parsers/graphql-parser.js +0 -2011
  38. package/parsers/html-parser.js +0 -1182
  39. package/parsers/js-parser.js +0 -2564
  40. package/parsers/node-parser.js +0 -2644
  41. package/parsers/php-parser.js +0 -3037
  42. package/parsers/react-parser.js +0 -1035
  43. package/parsers/ruby-parser.js +0 -2680
  44. package/parsers/ts-parser.js +0 -3881
@@ -1,2680 +0,0 @@
1
- const fs = require('fs')
2
- const AST = require('./ast-ruby')
3
-
4
- const RUBY_KEYWORDS = {
5
- 'def': 'def', 'définir': 'def', 'опр': 'def', '定义': 'def', '定義': 'def',
6
- 'fin': 'end', 'end': 'end', 'конец': 'end', '结束': 'end', '終了': 'end',
7
- 'classe': 'class', 'class': 'class', 'clase': 'class', 'класс': 'class', '类': 'class', 'クラス': 'class',
8
- 'moduleRuby': 'module', 'module': 'module', 'modulo': 'module', 'módulo': 'module', 'модуль': 'module', '模块': 'module', 'モジュール': 'module',
9
-
10
- 'vrai': 'true', 'true': 'true', 'verdadero': 'true', 'истина': 'true', '真': 'true', '真実': 'true',
11
- 'faux': 'false', 'false': 'false', 'falso': 'false', 'ложь': 'false', '假': 'false', '偽': 'false',
12
- 'nul': 'nil', 'nil': 'nil', 'nulo': 'nil', 'ноль': 'nil', '空': 'nil', 'ヌル': 'nil',
13
-
14
- 'si': 'if', 'if': 'if', 'если': 'if', '如果': 'if', 'もし': 'if',
15
- 'sinon': 'else', 'else': 'else', 'sino': 'else', 'иначе': 'else', '否则': 'else', 'そうでなければ': 'else',
16
- 'sinonsi': 'elsif', 'elsif': 'elsif', 'sinosi': 'elsif', 'иначеесли': 'elsif', '否则如果': 'elsif', 'でなければもし': 'elsif',
17
- 'saufsi': 'unless', 'unless': 'unless', 'amenos': 'unless', 'еслине': 'unless', '除非': 'unless', 'でなければ': 'unless',
18
- 'cas': 'case', 'case': 'case', 'caso': 'case', 'случай': 'case', '情况': 'case', 'ケース': 'case',
19
- 'quand': 'when', 'when': 'when', 'cuando': 'when', 'когда': 'when', '当': 'when', '時': 'when',
20
- 'alors': 'then', 'then': 'then', 'entonces': 'then', 'тогда': 'then', '那么': 'then', 'それなら': 'then',
21
-
22
- 'tantque': 'while', 'while': 'while', 'mientras': 'while', 'пока': 'while', 'の間': 'while', '循环': 'while',
23
- 'jusqua': 'until', "jusqu'à": 'until', 'until': 'until', 'hasta': 'until', 'допока': 'until', '直到': 'until', 'まで': 'until',
24
- 'pour': 'for', 'for': 'for', 'para': 'for', 'для': 'for', '对于': 'for', 'ため': 'for',
25
- 'dans': 'in', 'in': 'in', 'en': 'in', 'в': 'in', '在': 'in', 'の中': 'in',
26
- 'faire': 'do', 'do': 'do', 'hacer': 'do', 'делать': 'do', '做': 'do', 'する': 'do',
27
- 'boucle': 'loop', 'loop': 'loop', 'bucle': 'loop', 'цикл': 'loop', '循环': 'loop', 'ループ': 'loop',
28
-
29
- 'arreter': 'break', 'arrêter': 'break', 'break': 'break', 'romper': 'break', 'прервать': 'break', '中断': 'break', 'ブレーク': 'break',
30
- 'suivant': 'next', 'next': 'next', 'siguiente': 'next', 'следующий': 'next', '下一个': 'next', '次': 'next',
31
- 'refaire': 'redo', 'redo': 'redo', 'rehacer': 'redo', 'повторить': 'redo', '重做': 'redo', 'やり直し': 'redo',
32
- 'reessayer': 'retry', 'réessayer': 'retry', 'retry': 'retry', 'reintentar': 'retry', '重试': 'retry', '再試行': 'retry',
33
-
34
- 'retourner': 'return', 'return': 'return', 'retornar': 'return', 'вернуть': 'return', '返回': 'return', '戻る': 'return',
35
- 'ceder': 'yield', 'céder': 'yield', 'yield': 'yield', 'уступить': 'yield', '产出': 'yield', '譲る': 'yield',
36
- 'alias': 'alias', 'псевдоним': 'alias', '别名': 'alias', 'エイリアス': 'alias',
37
- 'indefinir': 'undef', 'indéfinir': 'undef', 'undef': 'undef', 'отменить': 'undef', '取消定义': 'undef', '未定義': 'undef',
38
-
39
- 'soi': 'self', 'self': 'self', 'yo': 'self', 'себя': 'self', '自己': 'self', '自分': 'self',
40
- 'super': 'super', 'супер': 'super', '超级': 'super', 'スーパー': 'super',
41
- 'inclure': 'include', 'include': 'include', 'incluir': 'include', 'включить': 'include', '包含': 'include', 'インクルード': 'include',
42
- 'etendre': 'extend', 'étendre': 'extend', 'extend': 'extend', 'extender': 'extend', 'расширить': 'extend', '扩展': 'extend', 'エクステンド': 'extend',
43
- 'prependre': 'prepend', 'prépendre': 'prepend', 'prepend': 'prepend', 'anteponer': 'prepend', 'предшествовать': 'prepend', '前置': 'prepend', 'プリペンド': 'prepend',
44
-
45
- 'debut': 'begin', 'début': 'begin', 'begin': 'begin', 'inicio': 'begin', 'начало': 'begin', '开始': 'begin', '開始': 'begin',
46
- 'secourir': 'rescue', 'rescue': 'rescue', 'rescatar': 'rescue', 'спасти': 'rescue', '救援': 'rescue', 'レスキュー': 'rescue',
47
- 'assurer': 'ensure', 'ensure': 'ensure', 'asegurar': 'ensure', 'обеспечить': 'ensure', '确保': 'ensure', '確保': 'ensure',
48
- 'lever': 'raise', 'raise': 'raise', 'lanzar': 'raise', 'вызвать': 'raise', '抛出': 'raise', '発生': 'raise',
49
-
50
- 'et': 'and', 'and': 'and', 'y': 'and', 'и': 'and', '且': 'and', 'かつ': 'and',
51
- 'ou': 'or', 'or': 'or', 'o': 'or', 'или': 'or', '或': 'or', 'または': 'or',
52
- 'non': 'not', 'not': 'not', 'no': 'not', 'не': 'not', '非': 'not', 'ではない': 'not',
53
- 'defini': 'defined?', 'défini': 'defined?', 'defined?': 'defined?', 'definido': 'defined?', 'определен': 'defined?', '已定义': 'defined?', '定義済み': 'defined?',
54
-
55
- 'exiger': 'require', 'require': 'require', 'requerir': 'require', 'требовать': 'require', '需要': 'require', '要求': 'require',
56
- 'exigerRelatif': 'require_relative', 'require_relative': 'require_relative', 'requerirRelativo': 'require_relative', 'требоватьОтносительно': 'require_relative', '相对需要': 'require_relative', '相対要求': 'require_relative',
57
- 'charger': 'load', 'load': 'load', 'cargar': 'load', 'загрузить': 'load', '加载': 'load', 'ロード': 'load',
58
-
59
- 'lambda': 'lambda', 'лямбда': 'lambda', 'ラムダ': 'lambda',
60
- 'proc': 'proc', 'процедура': 'proc', 'プロシージャ': 'proc',
61
-
62
- 'nouveau': 'new', 'new': 'new', 'nuevo': 'new', 'новый': 'new', '新': 'new', '新規': 'new',
63
-
64
- 'public': 'public', 'publico': 'public', 'público': 'public', 'публичный': 'public', '公开': 'public', 'パブリック': 'public',
65
- 'prive': 'private', 'privé': 'private', 'private': 'private', 'privado': 'private', 'приватный': 'private', '私有': 'private', 'プライベート': 'private',
66
- 'protege': 'protected', 'protégé': 'protected', 'protected': 'protected', 'protegido': 'protected', 'защищенный': 'protected', '受保护': 'protected', 'プロテクト': 'protected',
67
-
68
- 'attr_reader': 'attr_reader', 'attr_writer': 'attr_writer', 'attr_accessor': 'attr_accessor',
69
- 'afficher': 'puts', 'puts': 'puts', 'mostrar': 'puts', 'показать': 'puts', '打印': 'puts', '表示': 'puts',
70
- 'imprimer': 'print', 'print': 'print', 'imprimir': 'print', 'печать': 'print', '印刷': 'print',
71
- 'p': 'p'
72
- }
73
-
74
- const RUBY_METHODS = {
75
- 'majuscule': 'upcase', 'upcase': 'upcase', 'mayuscula': 'upcase', 'mayúscula': 'upcase', 'верхний': 'upcase', '大写': 'upcase', '大文字': 'upcase',
76
- 'minuscule': 'downcase', 'downcase': 'downcase', 'minuscula': 'downcase', 'minúscula': 'downcase', 'нижний': 'downcase', '小写': 'downcase', '小文字': 'downcase',
77
- 'capitaliser': 'capitalize', 'capitalize': 'capitalize', 'capitalizar': 'capitalize', 'капитализировать': 'capitalize', '首字母大写': 'capitalize', '大文字化': 'capitalize',
78
- 'inverserCasse': 'swapcase', 'swapcase': 'swapcase', 'intercambiarCaso': 'swapcase', 'сменитьРегистр': 'swapcase', '交换大小写': 'swapcase', '大小文字変換': 'swapcase',
79
- 'remplacerTout': 'gsub', 'gsub': 'gsub', 'reemplazarTodo': 'gsub', 'заменитьВсе': 'gsub', '全部替换': 'gsub', '全置換': 'gsub',
80
- 'remplacer': 'sub', 'sub': 'sub', 'reemplazar': 'sub', 'заменить': 'sub', '替换': 'sub', '置換': 'sub',
81
- 'decouper': 'split', 'découper': 'split', 'split': 'split', 'dividir': 'split', 'разделить': 'split', '分割': 'split',
82
- 'joindre': 'join', 'join': 'join', 'unir': 'join', 'соединить': 'join', '连接': 'join', '結合': 'join',
83
- 'nettoyer': 'strip', 'strip': 'strip', 'limpiar': 'strip', 'очистить': 'strip', '清除空白': 'strip', '空白除去': 'strip',
84
- 'nettoyerGauche': 'lstrip', 'lstrip': 'lstrip', 'limpiarIzquierda': 'lstrip', 'очиститьСлева': 'lstrip', '清除左空白': 'lstrip', '左空白除去': 'lstrip',
85
- 'nettoyerDroite': 'rstrip', 'rstrip': 'rstrip', 'limpiarDerecha': 'rstrip', 'очиститьСправа': 'rstrip', '清除右空白': 'rstrip', '右空白除去': 'rstrip',
86
- 'vide?': 'empty?', 'empty?': 'empty?', 'vacio?': 'empty?', 'vacío?': 'empty?', 'пусто?': 'empty?', '空?': 'empty?', '空か?': 'empty?',
87
- 'inclut?': 'include?', 'include?': 'include?', 'incluye?': 'include?', 'включает?': 'include?', '包含?': 'include?', '含む?': 'include?',
88
- 'commencePar?': 'start_with?', 'start_with?': 'start_with?', 'comienzaCon?': 'start_with?', 'начинаетсяС?': 'start_with?', '以开头?': 'start_with?', '始まる?': 'start_with?',
89
- 'terminePar?': 'end_with?', 'end_with?': 'end_with?', 'terminaCon?': 'end_with?', 'заканчиваетсяНа?': 'end_with?', '以结尾?': 'end_with?', '終わる?': 'end_with?',
90
- 'longueur': 'length', 'length': 'length', 'longitud': 'length', 'длина': 'length', '长度': 'length', '長さ': 'length',
91
- 'taille': 'size', 'size': 'size', 'tamano': 'size', 'tamaño': 'size', 'размер': 'size', '大小': 'size', 'サイズ': 'size',
92
- 'inverser': 'reverse', 'reverse': 'reverse', 'invertir': 'reverse', 'обратить': 'reverse', '反转': 'reverse', '逆順': 'reverse',
93
-
94
- 'ajouter': 'push', 'push': 'push', 'agregar': 'push', 'добавить': 'push', '添加': 'push', '追加': 'push',
95
- 'ajouterDebut': 'unshift', 'ajouterDébut': 'unshift', 'unshift': 'unshift', 'agregarInicio': 'unshift', 'добавитьВНачало': 'unshift', '添加到开头': 'unshift', '先頭追加': 'unshift',
96
- 'retirer': 'pop', 'pop': 'pop', 'quitar': 'pop', 'удалить': 'pop', '弹出': 'pop', '末尾削除': 'pop',
97
- 'retirerDebut': 'shift', 'retirerDébut': 'shift', 'shift': 'shift', 'quitarInicio': 'shift', 'удалитьИзНачала': 'shift', '弹出开头': 'shift', '先頭削除': 'shift',
98
- 'premier': 'first', 'first': 'first', 'primero': 'first', 'первый': 'first', '第一个': 'first', '最初': 'first',
99
- 'dernier': 'last', 'last': 'last', 'ultimo': 'last', 'último': 'last', 'последний': 'last', '最后一个': 'last', '最後': 'last',
100
- 'prendre': 'take', 'take': 'take', 'tomar': 'take', 'взять': 'take', '取': 'take', '取得': 'take',
101
- 'laisser': 'drop', 'drop': 'drop', 'dejar': 'drop', 'отбросить': 'drop', '丢弃': 'drop', '破棄': 'drop',
102
- 'unique': 'uniq', 'uniq': 'uniq', 'unico': 'uniq', 'único': 'uniq', 'уникальный': 'uniq', '唯一': 'uniq', '一意': 'uniq',
103
- 'compacter': 'compact', 'compact': 'compact', 'compactar': 'compact', 'уплотнить': 'compact', '压缩': 'compact', 'コンパクト': 'compact',
104
- 'aplatir': 'flatten', 'flatten': 'flatten', 'aplanar': 'flatten', 'сплющить': 'flatten', '扁平化': 'flatten', 'フラット化': 'flatten',
105
- 'trier': 'sort', 'sort': 'sort', 'ordenar': 'sort', 'сортировать': 'sort', '排序': 'sort', 'ソート': 'sort',
106
- 'trierPar': 'sort_by', 'sort_by': 'sort_by', 'ordenarPor': 'sort_by', 'сортироватьПо': 'sort_by', '按排序': 'sort_by', 'ソートバイ': 'sort_by',
107
- 'melanger': 'shuffle', 'mélanger': 'shuffle', 'shuffle': 'shuffle', 'mezclar': 'shuffle', 'перемешать': 'shuffle', '洗牌': 'shuffle', 'シャッフル': 'shuffle',
108
- 'echantillon': 'sample', 'échantillon': 'sample', 'sample': 'sample', 'muestra': 'sample', 'образец': 'sample', '样本': 'sample', 'サンプル': 'sample',
109
-
110
- 'transformer': 'map', 'map': 'map', 'transformar': 'map', 'преобразовать': 'map', '映射': 'map', 'マップ': 'map',
111
- 'selectionner': 'select', 'sélectionner': 'select', 'select': 'select', 'seleccionar': 'select', 'выбрать': 'select', '选择': 'select', '選択': 'select',
112
- 'rejeter': 'reject', 'reject': 'reject', 'rechazar': 'reject', 'отклонить': 'reject', '拒绝': 'reject', '却下': 'reject',
113
- 'trouver': 'find', 'find': 'find', 'encontrar': 'find', 'найти': 'find', '查找': 'find', '見つける': 'find',
114
- 'chaque': 'each', 'each': 'each', 'cada': 'each', 'каждый': 'each', '每个': 'each', '各': 'each',
115
- 'chacunAvecIndex': 'each_with_index', 'each_with_index': 'each_with_index', 'cadaConIndice': 'each_with_index', 'каждыйСИндексом': 'each_with_index', '每个带索引': 'each_with_index', '各インデックス付き': 'each_with_index',
116
- 'reduire': 'reduce', 'réduire': 'reduce', 'reduce': 'reduce', 'reducir': 'reduce', 'уменьшить': 'reduce', '减少': 'reduce', '畳み込み': 'reduce',
117
- 'injecter': 'inject', 'inject': 'inject', 'inyectar': 'inject', 'инъекция': 'inject', '注入': 'inject', 'インジェクト': 'inject',
118
- 'tout?': 'all?', 'all?': 'all?', 'todo?': 'all?', 'все?': 'all?', '全部?': 'all?', '全て?': 'all?',
119
- 'quelconque?': 'any?', 'any?': 'any?', 'alguno?': 'any?', 'любой?': 'any?', '任意?': 'any?', '任意か?': 'any?',
120
- 'aucun?': 'none?', 'none?': 'none?', 'ninguno?': 'none?', 'никакой?': 'none?', '无?': 'none?', 'なし?': 'none?',
121
- 'compter': 'count', 'count': 'count', 'contar': 'count', 'считать': 'count', '计数': 'count', 'カウント': 'count',
122
- 'groupePar': 'group_by', 'group_by': 'group_by', 'agruparPor': 'group_by', 'группироватьПо': 'group_by', '分组按': 'group_by', 'グループ化': 'group_by',
123
- 'partition': 'partition', 'particion': 'partition', 'partición': 'partition', 'раздел': 'partition', '分区': 'partition', 'パーティション': 'partition',
124
- 'min': 'min', 'mínimo': 'min', 'минимум': 'min', '最小': 'min',
125
- 'max': 'max', 'máximo': 'max', 'максимум': 'max', '最大': 'max',
126
- 'somme': 'sum', 'sum': 'sum', 'suma': 'sum', 'сумма': 'sum', '总和': 'sum', '合計': 'sum',
127
-
128
- 'cles': 'keys', 'clés': 'keys', 'keys': 'keys', 'llaves': 'keys', 'ключи': 'keys', '键': 'keys', 'キー': 'keys',
129
- 'valeurs': 'values', 'values': 'values', 'valores': 'values', 'значения': 'values', '值': 'values', '値': 'values',
130
- 'fusionner': 'merge', 'merge': 'merge', 'fusionar': 'merge', 'слить': 'merge', '合并': 'merge', 'マージ': 'merge',
131
- 'recuperer': 'fetch', 'récupérer': 'fetch', 'fetch': 'fetch', 'recuperar': 'fetch', 'получить': 'fetch', '获取': 'fetch', 'フェッチ': 'fetch',
132
- 'supprimer': 'delete', 'delete': 'delete', 'eliminar': 'delete', 'удалить': 'delete', '删除': 'delete', '削除': 'delete',
133
- 'aCle?': 'has_key?', 'aClé?': 'has_key?', 'has_key?': 'has_key?', 'key?': 'key?', 'tieneLlave?': 'has_key?', 'имеетКлюч?': 'has_key?', '有键?': 'has_key?', 'キーあり?': 'has_key?',
134
- 'aValeur?': 'has_value?', 'has_value?': 'has_value?', 'value?': 'value?', 'tieneValor?': 'has_value?', 'имеетЗначение?': 'has_value?', '有值?': 'has_value?', '値あり?': 'has_value?',
135
-
136
- 'versEntier': 'to_i', 'to_i': 'to_i', 'aEntero': 'to_i', 'вЦелое': 'to_i', '转整数': 'to_i', '整数へ': 'to_i',
137
- 'versFlottant': 'to_f', 'to_f': 'to_f', 'aFlotante': 'to_f', 'вДробное': 'to_f', '转浮点数': 'to_f', '浮動小数点へ': 'to_f',
138
- 'versChaine': 'to_s', 'to_s': 'to_s', 'aCadena': 'to_s', 'вСтроку': 'to_s', '转字符串': 'to_s', '文字列へ': 'to_s',
139
- 'versSymbole': 'to_sym', 'to_sym': 'to_sym', 'aSimbolo': 'to_sym', 'aСímbolo': 'to_sym', 'вСимвол': 'to_sym', '转符号': 'to_sym', 'シンボルへ': 'to_sym',
140
- 'versTableau': 'to_a', 'to_a': 'to_a', 'aArreglo': 'to_a', 'вМассив': 'to_a', '转数组': 'to_a', '配列へ': 'to_a',
141
- 'versHash': 'to_h', 'to_h': 'to_h', 'aHash': 'to_h', 'вХеш': 'to_h', '转哈希': 'to_h', 'ハッシュへ': 'to_h',
142
-
143
- 'appeler': 'call', 'call': 'call', 'llamar': 'call', 'вызвать': 'call', '调用': 'call', '呼び出し': 'call',
144
- 'ouvrir': 'open', 'open': 'open', 'abrir': 'open', 'открыть': 'open', '打开': 'open', '開く': 'open',
145
- 'lire': 'read', 'read': 'read', 'leer': 'read', 'читать': 'read', '读取': 'read', '読む': 'read',
146
- 'ecrire': 'write', 'écrire': 'write', 'write': 'write', 'escribir': 'write', 'писать': 'write', '写入': 'write', '書く': 'write',
147
- 'fermer': 'close', 'close': 'close', 'cerrar': 'close', 'закрыть': 'close', '关闭': 'close', '閉じる': 'close',
148
- 'chaqueLigne': 'each_line', 'each_line': 'each_line', 'cadaLinea': 'each_line', 'cadaLínea': 'each_line', 'каждаяСтрока': 'each_line', '每行': 'each_line', '各行': 'each_line',
149
- 'lireFichier': 'read', 'ecrireFichier': 'write', 'écrireFichier': 'write',
150
-
151
- 'fois': 'times', 'times': 'times', 'veces': 'times', 'раз': 'times', '次': 'times', '回': 'times',
152
- 'jusqua': 'upto', "jusqu'à": 'upto', 'upto': 'upto', 'hasta': 'upto', 'до': 'upto', '到': 'upto', 'まで': 'upto',
153
- 'depuis': 'downto', 'downto': 'downto', 'desde': 'downto', 'от': 'downto', '从': 'downto', 'から': 'downto',
154
- 'pas': 'step', 'step': 'step', 'paso': 'step', 'шаг': 'step', '步': 'step', 'ステップ': 'step',
155
-
156
- 'pair?': 'even?', 'even?': 'even?', 'par?': 'even?', 'четный?': 'even?', '偶数?': 'even?',
157
- 'impair?': 'odd?', 'odd?': 'odd?', 'impar?': 'odd?', 'нечетный?': 'odd?', '奇数?': 'odd?',
158
- 'zero?': 'zero?', 'zéro?': 'zero?', 'cero?': 'zero?', 'ноль?': 'zero?', '零?': 'zero?', 'ゼロ?': 'zero?',
159
- 'positif?': 'positive?', 'positive?': 'positive?', 'positivo?': 'positive?', 'положительный?': 'positive?', '正数?': 'positive?', '正?': 'positive?',
160
- 'negatif?': 'negative?', 'négatif?': 'negative?', 'negative?': 'negative?', 'negativo?': 'negative?', 'отрицательный?': 'negative?', '负数?': 'negative?', '負?': 'negative?',
161
- 'abs': 'abs', 'absolu': 'abs', 'absoluto': 'abs', 'абсолютный': 'abs', '绝对值': 'abs', '絶対値': 'abs',
162
- 'arrondi': 'round', 'round': 'round', 'redondear': 'round', 'округлить': 'round', '四舍五入': 'round', '丸め': 'round',
163
- 'plancher': 'floor', 'floor': 'floor', 'piso': 'floor', 'пол': 'floor', '向下取整': 'floor', '切り捨て': 'floor',
164
- 'plafond': 'ceil', 'ceil': 'ceil', 'techo': 'ceil', 'потолок': 'ceil', '向上取整': 'ceil', '切り上げ': 'ceil',
165
-
166
- 'initialiser': 'initialize', 'initialize': 'initialize', 'inicializar': 'initialize', 'инициализировать': 'initialize', '初始化': 'initialize', '初期化': 'initialize',
167
-
168
- 'repondA?': 'respond_to?', 'répondÀ?': 'respond_to?', 'respond_to?': 'respond_to?', 'respondeA?': 'respond_to?', 'отвечаетНа?': 'respond_to?', '响应?': 'respond_to?', '応答する?': 'respond_to?',
169
- 'estUn?': 'is_a?', 'is_a?': 'is_a?', 'kind_of?': 'kind_of?', 'esUn?': 'is_a?', 'являетсяЛи?': 'is_a?', '是?': 'is_a?', 'である?': 'is_a?',
170
- 'instanceDe?': 'instance_of?', 'instance_of?': 'instance_of?', 'instanciaDe?': 'instance_of?', 'экземплярЛи?': 'instance_of?', '实例?': 'instance_of?', 'インスタンス?': 'instance_of?',
171
- 'methodes': 'methods', 'méthodes': 'methods', 'methods': 'methods', 'metodos': 'methods', 'métodos': 'methods', 'методы': 'methods', '方法列表': 'methods', 'メソッド一覧': 'methods',
172
- 'envoyer': 'send', 'send': 'send', 'enviar': 'send', 'отправить': 'send', '发送': 'send', '送る': 'send',
173
- 'methode': 'method', 'méthode': 'method', 'method': 'method', 'metodo': 'method', 'método': 'method', 'метод': 'method', '方法': 'method', 'メソッド': 'method',
174
- 'tap': 'tap', 'then': 'then', 'yield_self': 'yield_self'
175
- }
176
-
177
- class RubyLexer {
178
- constructor(source, i18n = {}) {
179
- this.source = source
180
- this.pos = 0
181
- this.line = 1
182
- this.column = 1
183
- this.tokens = []
184
- this.i18n = i18n
185
- }
186
-
187
- peek(offset = 0) {
188
- return this.source[this.pos + offset] || ''
189
- }
190
-
191
- advance() {
192
- const char = this.source[this.pos]
193
- this.pos++
194
- if (char === '\n') {
195
- this.line++
196
- this.column = 1
197
- } else {
198
- this.column++
199
- }
200
- return char
201
- }
202
-
203
- skipWhitespace() {
204
- while (this.pos < this.source.length && /[ \t]/.test(this.peek())) {
205
- this.advance()
206
- }
207
- }
208
-
209
- skipComment() {
210
- if (this.peek() === '#') {
211
- while (this.pos < this.source.length && this.peek() !== '\n') {
212
- this.advance()
213
- }
214
- return true
215
- }
216
- if (this.peek() === '=' && this.peek(1) === 'b' && this.peek(2) === 'e' && this.peek(3) === 'g' && this.peek(4) === 'i' && this.peek(5) === 'n') {
217
- while (this.pos < this.source.length) {
218
- if (this.peek() === '=' && this.source.slice(this.pos, this.pos + 4) === '=end') {
219
- this.pos += 4
220
- break
221
- }
222
- this.advance()
223
- }
224
- return true
225
- }
226
- return false
227
- }
228
-
229
- tokenize() {
230
- while (this.pos < this.source.length) {
231
- this.skipWhitespace()
232
- while (this.skipComment()) {
233
- this.skipWhitespace()
234
- }
235
- if (this.pos >= this.source.length) break
236
-
237
- const loc = { line: this.line, column: this.column }
238
- const char = this.peek()
239
-
240
- if (char === '\n' || char === ';') {
241
- this.advance()
242
- this.tokens.push({ type: 'NEWLINE', loc })
243
- continue
244
- }
245
-
246
- if (char === '"' || char === "'") {
247
- this.tokens.push(this.readString(char))
248
- continue
249
- }
250
-
251
- if (char === '`') {
252
- this.tokens.push(this.readBacktick())
253
- continue
254
- }
255
-
256
- if (char === ':' && /[a-zA-Z_]/.test(this.peek(1))) {
257
- this.tokens.push(this.readSymbol())
258
- continue
259
- }
260
-
261
- if (char === '%') {
262
- const next = this.peek(1)
263
- if ('wWiIqQrxs'.includes(next)) {
264
- this.tokens.push(this.readPercentLiteral())
265
- continue
266
- }
267
- }
268
-
269
- if (char === '/' && this.shouldBeRegex()) {
270
- this.tokens.push(this.readRegexp())
271
- continue
272
- }
273
-
274
- if (/[0-9]/.test(char) || (char === '.' && /[0-9]/.test(this.peek(1)))) {
275
- this.tokens.push(this.readNumber())
276
- continue
277
- }
278
-
279
- if (char === '@') {
280
- this.tokens.push(this.readInstanceOrClassVar())
281
- continue
282
- }
283
-
284
- if (char === '$') {
285
- this.tokens.push(this.readGlobalVar())
286
- continue
287
- }
288
-
289
- if (/[a-zA-ZÀ-ÿА-яぁ-ゟァ-ヿ一-龯_]/.test(char)) {
290
- this.tokens.push(this.readWord())
291
- continue
292
- }
293
-
294
- const token = this.readOperator()
295
- if (token) {
296
- this.tokens.push(token)
297
- continue
298
- }
299
-
300
- this.advance()
301
- }
302
-
303
- this.tokens.push({ type: 'EOF', loc: { line: this.line, column: this.column } })
304
- return this.tokens
305
- }
306
-
307
- shouldBeRegex() {
308
- if (this.tokens.length === 0) return true
309
- const last = this.tokens[this.tokens.length - 1]
310
- const noRegexAfter = ['IDENTIFIER', 'CONSTANT', 'NUMBER', 'STRING', 'SYMBOL', 'RPAREN', 'RBRACKET', 'RBRACE', 'INSTANCE_VAR', 'CLASS_VAR', 'GLOBAL_VAR']
311
- return !noRegexAfter.includes(last.type)
312
- }
313
-
314
- readWord() {
315
- const loc = { line: this.line, column: this.column }
316
- let word = ''
317
-
318
- while (this.pos < this.source.length && /[a-zA-Z0-9À-ÿА-яぁ-ゟァ-ヿ一-龯_?!]/.test(this.peek())) {
319
- word += this.advance()
320
- }
321
-
322
- if (/^[A-Z]/.test(word)) {
323
- return { type: 'CONSTANT', value: word, loc }
324
- }
325
-
326
- if (RUBY_KEYWORDS[word]) {
327
- return { type: 'KEYWORD', value: word, loc }
328
- }
329
-
330
- if (RUBY_METHODS[word]) {
331
- return { type: 'METHOD', value: word, loc }
332
- }
333
-
334
- return { type: 'IDENTIFIER', value: word, loc }
335
- }
336
-
337
- readInstanceOrClassVar() {
338
- const loc = { line: this.line, column: this.column }
339
- this.advance()
340
-
341
- if (this.peek() === '@') {
342
- this.advance()
343
- let name = ''
344
- while (this.pos < this.source.length && /[a-zA-Z0-9_]/.test(this.peek())) {
345
- name += this.advance()
346
- }
347
- return { type: 'CLASS_VAR', value: name, loc }
348
- }
349
-
350
- let name = ''
351
- while (this.pos < this.source.length && /[a-zA-Z0-9_]/.test(this.peek())) {
352
- name += this.advance()
353
- }
354
- return { type: 'INSTANCE_VAR', value: name, loc }
355
- }
356
-
357
- readGlobalVar() {
358
- const loc = { line: this.line, column: this.column }
359
- this.advance()
360
-
361
- let name = ''
362
- if (/[0-9~!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?`]/.test(this.peek())) {
363
- name = this.advance()
364
- } else {
365
- while (this.pos < this.source.length && /[a-zA-Z0-9_]/.test(this.peek())) {
366
- name += this.advance()
367
- }
368
- }
369
- return { type: 'GLOBAL_VAR', value: name, loc }
370
- }
371
-
372
- readSymbol() {
373
- const loc = { line: this.line, column: this.column }
374
- this.advance()
375
-
376
- if (this.peek() === '"') {
377
- this.advance()
378
- let value = ''
379
- while (this.pos < this.source.length && this.peek() !== '"') {
380
- if (this.peek() === '\\') {
381
- this.advance()
382
- value += this.advance()
383
- } else {
384
- value += this.advance()
385
- }
386
- }
387
- this.advance()
388
- return { type: 'SYMBOL', value, loc }
389
- }
390
-
391
- let value = ''
392
- while (this.pos < this.source.length && /[a-zA-Z0-9_?!=]/.test(this.peek())) {
393
- value += this.advance()
394
- }
395
- return { type: 'SYMBOL', value, loc }
396
- }
397
-
398
- readString(quote) {
399
- const loc = { line: this.line, column: this.column }
400
- this.advance()
401
-
402
- let value = ''
403
- let raw = quote
404
- const parts = []
405
- let currentPart = ''
406
- let interpolated = false
407
-
408
- while (this.pos < this.source.length && this.peek() !== quote) {
409
- if (this.peek() === '\\') {
410
- raw += this.advance()
411
- if (this.pos < this.source.length) {
412
- const escaped = this.advance()
413
- raw += escaped
414
- switch (escaped) {
415
- case 'n': currentPart += '\n'; break
416
- case 'r': currentPart += '\r'; break
417
- case 't': currentPart += '\t'; break
418
- case '\\': currentPart += '\\'; break
419
- case quote: currentPart += quote; break
420
- default: currentPart += '\\' + escaped
421
- }
422
- }
423
- } else if (quote === '"' && this.peek() === '#' && this.peek(1) === '{') {
424
- interpolated = true
425
- if (currentPart) {
426
- parts.push({ type: 'string', value: currentPart })
427
- currentPart = ''
428
- }
429
- raw += this.advance() + this.advance()
430
- let braceCount = 1
431
- let expr = ''
432
- while (braceCount > 0 && this.pos < this.source.length) {
433
- const c = this.peek()
434
- if (c === '{') braceCount++
435
- if (c === '}') braceCount--
436
- if (braceCount > 0) {
437
- expr += this.advance()
438
- raw += expr[expr.length - 1]
439
- } else {
440
- raw += this.advance()
441
- }
442
- }
443
- parts.push({ type: 'interpolation', value: expr })
444
- } else {
445
- const c = this.advance()
446
- currentPart += c
447
- raw += c
448
- }
449
- }
450
-
451
- if (currentPart) {
452
- parts.push({ type: 'string', value: currentPart })
453
- }
454
-
455
- if (this.peek() === quote) {
456
- raw += this.advance()
457
- }
458
-
459
- if (interpolated) {
460
- return { type: 'INTERPOLATED_STRING', parts, raw, loc }
461
- }
462
-
463
- return { type: 'STRING', value: parts.map(p => p.value).join(''), raw, loc }
464
- }
465
-
466
- readBacktick() {
467
- const loc = { line: this.line, column: this.column }
468
- this.advance()
469
-
470
- let command = ''
471
- while (this.pos < this.source.length && this.peek() !== '`') {
472
- if (this.peek() === '\\') {
473
- this.advance()
474
- command += this.advance()
475
- } else {
476
- command += this.advance()
477
- }
478
- }
479
- this.advance()
480
- return { type: 'BACKTICK', value: command, loc }
481
- }
482
-
483
- readRegexp() {
484
- const loc = { line: this.line, column: this.column }
485
- this.advance()
486
-
487
- let pattern = ''
488
- while (this.pos < this.source.length && this.peek() !== '/') {
489
- if (this.peek() === '\\') {
490
- pattern += this.advance()
491
- pattern += this.advance()
492
- } else {
493
- pattern += this.advance()
494
- }
495
- }
496
- this.advance()
497
-
498
- let flags = ''
499
- while (/[imxo]/.test(this.peek())) {
500
- flags += this.advance()
501
- }
502
-
503
- return { type: 'REGEXP', pattern, flags, loc }
504
- }
505
-
506
- readPercentLiteral() {
507
- const loc = { line: this.line, column: this.column }
508
- this.advance()
509
- const type = this.advance()
510
- const delimiter = this.advance()
511
- const closeDelimiter = { '(': ')', '[': ']', '{': '}', '<': '>' }[delimiter] || delimiter
512
-
513
- const elements = []
514
- let current = ''
515
- let depth = 1
516
-
517
- while (this.pos < this.source.length && depth > 0) {
518
- const c = this.peek()
519
- if (c === delimiter && delimiter !== closeDelimiter) {
520
- depth++
521
- current += this.advance()
522
- } else if (c === closeDelimiter) {
523
- depth--
524
- if (depth > 0) current += this.advance()
525
- else this.advance()
526
- } else if (/\s/.test(c) && depth === 1 && 'wWiI'.includes(type)) {
527
- if (current) elements.push(current)
528
- current = ''
529
- this.advance()
530
- } else {
531
- current += this.advance()
532
- }
533
- }
534
- if (current) elements.push(current)
535
-
536
- return { type: 'PERCENT_LITERAL', literalType: type, elements, loc }
537
- }
538
-
539
- readNumber() {
540
- const loc = { line: this.line, column: this.column }
541
- let value = ''
542
- let isFloat = false
543
- let isRational = false
544
- let isComplex = false
545
-
546
- if (this.peek() === '0' && (this.peek(1) === 'x' || this.peek(1) === 'X')) {
547
- value += this.advance() + this.advance()
548
- while (/[0-9a-fA-F_]/.test(this.peek())) {
549
- value += this.advance()
550
- }
551
- } else if (this.peek() === '0' && (this.peek(1) === 'b' || this.peek(1) === 'B')) {
552
- value += this.advance() + this.advance()
553
- while (/[01_]/.test(this.peek())) {
554
- value += this.advance()
555
- }
556
- } else if (this.peek() === '0' && (this.peek(1) === 'o' || this.peek(1) === 'O')) {
557
- value += this.advance() + this.advance()
558
- while (/[0-7_]/.test(this.peek())) {
559
- value += this.advance()
560
- }
561
- } else {
562
- while (/[0-9_]/.test(this.peek())) {
563
- value += this.advance()
564
- }
565
-
566
- if (this.peek() === '.' && /[0-9]/.test(this.peek(1))) {
567
- isFloat = true
568
- value += this.advance()
569
- while (/[0-9_]/.test(this.peek())) {
570
- value += this.advance()
571
- }
572
- }
573
-
574
- if (this.peek() === 'e' || this.peek() === 'E') {
575
- isFloat = true
576
- value += this.advance()
577
- if (this.peek() === '+' || this.peek() === '-') {
578
- value += this.advance()
579
- }
580
- while (/[0-9_]/.test(this.peek())) {
581
- value += this.advance()
582
- }
583
- }
584
- }
585
-
586
- if (this.peek() === 'r') {
587
- isRational = true
588
- this.advance()
589
- } else if (this.peek() === 'i') {
590
- isComplex = true
591
- this.advance()
592
- }
593
-
594
- const cleanValue = value.replace(/_/g, '')
595
-
596
- if (isRational) {
597
- return { type: 'RATIONAL', value: cleanValue, raw: value + 'r', loc }
598
- }
599
- if (isComplex) {
600
- return { type: 'COMPLEX', value: cleanValue, raw: value + 'i', loc }
601
- }
602
- if (isFloat) {
603
- return { type: 'FLOAT', value: parseFloat(cleanValue), raw: value, loc }
604
- }
605
-
606
- let numValue
607
- if (value.startsWith('0x') || value.startsWith('0X')) {
608
- numValue = parseInt(cleanValue.slice(2), 16)
609
- } else if (value.startsWith('0b') || value.startsWith('0B')) {
610
- numValue = parseInt(cleanValue.slice(2), 2)
611
- } else if (value.startsWith('0o') || value.startsWith('0O')) {
612
- numValue = parseInt(cleanValue.slice(2), 8)
613
- } else {
614
- numValue = parseInt(cleanValue, 10)
615
- }
616
-
617
- return { type: 'NUMBER', value: numValue, raw: value, loc }
618
- }
619
-
620
- readOperator() {
621
- const loc = { line: this.line, column: this.column }
622
-
623
- const threeChar = this.source.slice(this.pos, this.pos + 3)
624
- const twoChar = this.source.slice(this.pos, this.pos + 2)
625
- const oneChar = this.peek()
626
-
627
- const threeCharOps = {
628
- '...': 'EXCLUSIVE_RANGE', '<=>': 'SPACESHIP', '===': 'CASE_EQUAL',
629
- '&&=': 'AND_ASSIGN', '||=': 'OR_ASSIGN', '**=': 'POW_ASSIGN',
630
- '<<=': 'LSHIFT_ASSIGN', '>>=': 'RSHIFT_ASSIGN', '&.': 'SAFE_NAV'
631
- }
632
-
633
- const twoCharOps = {
634
- '==': 'EQUAL', '!=': 'NOT_EQUAL', '<=': 'LTE', '>=': 'GTE',
635
- '&&': 'AND', '||': 'OR', '..': 'INCLUSIVE_RANGE',
636
- '<<': 'LSHIFT', '>>': 'RSHIFT', '**': 'POW',
637
- '+=': 'PLUS_ASSIGN', '-=': 'MINUS_ASSIGN', '*=': 'TIMES_ASSIGN',
638
- '/=': 'DIV_ASSIGN', '%=': 'MOD_ASSIGN', '&=': 'BIT_AND_ASSIGN',
639
- '|=': 'BIT_OR_ASSIGN', '^=': 'BIT_XOR_ASSIGN',
640
- '=~': 'MATCH', '!~': 'NOT_MATCH', '=>': 'HASH_ROCKET',
641
- '::': 'SCOPE', '->': 'LAMBDA_ARROW', '&.': 'SAFE_NAV'
642
- }
643
-
644
- const oneCharOps = {
645
- '+': 'PLUS', '-': 'MINUS', '*': 'TIMES', '/': 'DIV', '%': 'MOD',
646
- '=': 'ASSIGN', '<': 'LT', '>': 'GT', '!': 'NOT', '~': 'TILDE',
647
- '&': 'AMPERSAND', '|': 'PIPE', '^': 'CARET',
648
- '(': 'LPAREN', ')': 'RPAREN', '[': 'LBRACKET', ']': 'RBRACKET',
649
- '{': 'LBRACE', '}': 'RBRACE', ',': 'COMMA', '.': 'DOT',
650
- ':': 'COLON', '?': 'QUESTION', '\\': 'BACKSLASH'
651
- }
652
-
653
- if (threeCharOps[threeChar]) {
654
- this.pos += 3
655
- return { type: threeCharOps[threeChar], value: threeChar, loc }
656
- }
657
-
658
- if (twoCharOps[twoChar]) {
659
- this.pos += 2
660
- return { type: twoCharOps[twoChar], value: twoChar, loc }
661
- }
662
-
663
- if (oneCharOps[oneChar]) {
664
- this.advance()
665
- return { type: oneCharOps[oneChar], value: oneChar, loc }
666
- }
667
-
668
- return null
669
- }
670
- }
671
-
672
- class RubyParser {
673
- constructor(i18nPath = null) {
674
- this.tokens = []
675
- this.pos = 0
676
- this.i18n = {}
677
-
678
- if (i18nPath) {
679
- this.loadI18n(i18nPath)
680
- }
681
- }
682
-
683
- loadI18n(filePath) {
684
- try {
685
- const content = fs.readFileSync(filePath, 'utf-8')
686
- this.i18n = JSON.parse(content)
687
- } catch (e) {
688
- this.i18n = {}
689
- }
690
- }
691
-
692
- parse(source) {
693
- const lexer = new RubyLexer(source, this.i18n)
694
- this.tokens = lexer.tokenize()
695
- this.pos = 0
696
- return this.parseProgram()
697
- }
698
-
699
- peek(offset = 0) {
700
- return this.tokens[this.pos + offset] || { type: 'EOF' }
701
- }
702
-
703
- advance() {
704
- return this.tokens[this.pos++]
705
- }
706
-
707
- match(...types) {
708
- return types.includes(this.peek().type)
709
- }
710
-
711
- expect(type) {
712
- const token = this.peek()
713
- if (token.type !== type) {
714
- throw new Error(`Attendu ${type}, reçu ${token.type} à ligne ${token.loc?.line}`)
715
- }
716
- return this.advance()
717
- }
718
-
719
- skipNewlines() {
720
- while (this.match('NEWLINE')) {
721
- this.advance()
722
- }
723
- }
724
-
725
- translateKeyword(word) {
726
- return RUBY_KEYWORDS[word] || word
727
- }
728
-
729
- translateMethod(word) {
730
- return RUBY_METHODS[word] || word
731
- }
732
-
733
- parseProgram() {
734
- const body = []
735
- this.skipNewlines()
736
-
737
- while (!this.match('EOF')) {
738
- const stmt = this.parseStatement()
739
- if (stmt) body.push(stmt)
740
- this.skipNewlines()
741
- }
742
-
743
- return new AST.Program(body)
744
- }
745
-
746
- parseStatement() {
747
- this.skipNewlines()
748
- const token = this.peek()
749
-
750
- if (token.type === 'KEYWORD') {
751
- const kw = this.translateKeyword(token.value)
752
-
753
- switch (kw) {
754
- case 'def': return this.parseMethodDefinition()
755
- case 'class': return this.parseClassDefinition()
756
- case 'module': return this.parseModuleDefinition()
757
- case 'if': return this.parseIfStatement()
758
- case 'unless': return this.parseUnlessStatement()
759
- case 'case': return this.parseCaseStatement()
760
- case 'while': return this.parseWhileStatement()
761
- case 'until': return this.parseUntilStatement()
762
- case 'for': return this.parseForStatement()
763
- case 'loop': return this.parseLoopStatement()
764
- case 'begin': return this.parseBeginBlock()
765
- case 'return': return this.parseReturnStatement()
766
- case 'break': return this.parseBreakStatement()
767
- case 'next': return this.parseNextStatement()
768
- case 'redo': return this.parseRedoStatement()
769
- case 'retry': return this.parseRetryStatement()
770
- case 'raise': return this.parseRaiseStatement()
771
- case 'require': return this.parseRequireStatement()
772
- case 'require_relative': return this.parseRequireRelativeStatement()
773
- case 'load': return this.parseLoadStatement()
774
- case 'include': return this.parseIncludeStatement()
775
- case 'extend': return this.parseExtendStatement()
776
- case 'prepend': return this.parsePrependStatement()
777
- case 'alias': return this.parseAliasStatement()
778
- case 'undef': return this.parseUndefStatement()
779
- case 'public':
780
- case 'private':
781
- case 'protected': return this.parseVisibilityStatement()
782
- case 'attr_reader':
783
- case 'attr_writer':
784
- case 'attr_accessor': return this.parseAttrAccessor()
785
- case 'puts':
786
- case 'print':
787
- case 'p': return this.parsePrintStatement()
788
- case 'end': return null
789
- }
790
- }
791
-
792
- const expr = this.parseExpression()
793
- return new AST.ExpressionStatement(expr, token.loc)
794
- }
795
-
796
- parseMethodDefinition() {
797
- const loc = this.peek().loc
798
- this.advance()
799
-
800
- let singleton = null
801
- let name = ''
802
-
803
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'self') {
804
- singleton = new AST.SelfExpression()
805
- this.advance()
806
- this.expect('DOT')
807
- }
808
-
809
- if (this.match('IDENTIFIER', 'METHOD', 'KEYWORD')) {
810
- name = this.advance().value
811
- } else if (this.match('CONSTANT')) {
812
- name = this.advance().value
813
- }
814
-
815
- if (this.peek().value === '=' || this.peek().value === '?' || this.peek().value === '!') {
816
- name += this.advance().value
817
- }
818
-
819
- const parameters = []
820
- if (this.match('LPAREN')) {
821
- this.advance()
822
- while (!this.match('RPAREN', 'EOF')) {
823
- parameters.push(this.parseMethodParameter())
824
- if (this.match('COMMA')) this.advance()
825
- }
826
- this.expect('RPAREN')
827
- } else if (this.match('IDENTIFIER', 'TIMES', 'POW', 'AMPERSAND')) {
828
- while (!this.match('NEWLINE', 'EOF')) {
829
- parameters.push(this.parseMethodParameter())
830
- if (this.match('COMMA')) this.advance()
831
- else break
832
- }
833
- }
834
-
835
- this.skipNewlines()
836
- const body = this.parseBody('end')
837
-
838
- return new AST.MethodDefinition(name, parameters, body, singleton, loc)
839
- }
840
-
841
- parseMethodParameter() {
842
- const loc = this.peek().loc
843
- let splat = false
844
- let doubleSplat = false
845
- let blockArg = false
846
- let keyword = false
847
-
848
- if (this.match('TIMES')) {
849
- splat = true
850
- this.advance()
851
- } else if (this.match('POW')) {
852
- doubleSplat = true
853
- this.advance()
854
- } else if (this.match('AMPERSAND')) {
855
- blockArg = true
856
- this.advance()
857
- }
858
-
859
- const name = this.expect('IDENTIFIER').value
860
-
861
- if (this.match('COLON')) {
862
- keyword = true
863
- this.advance()
864
- }
865
-
866
- let defaultValue = null
867
- if (this.match('ASSIGN')) {
868
- this.advance()
869
- defaultValue = this.parseExpression()
870
- }
871
-
872
- return new AST.MethodParameter(name, defaultValue, splat, doubleSplat, blockArg, keyword, loc)
873
- }
874
-
875
- parseClassDefinition() {
876
- const loc = this.peek().loc
877
- this.advance()
878
-
879
- if (this.match('LSHIFT')) {
880
- this.advance()
881
- const object = this.parseExpression()
882
- this.skipNewlines()
883
- const body = this.parseBody('end')
884
- return new AST.SingletonClass(object, body, loc)
885
- }
886
-
887
- const name = this.parseConstantPath()
888
-
889
- let superclass = null
890
- if (this.match('LT')) {
891
- this.advance()
892
- superclass = this.parseConstantPath()
893
- }
894
-
895
- this.skipNewlines()
896
- const body = this.parseBody('end')
897
-
898
- return new AST.ClassDefinition(name, superclass, body, loc)
899
- }
900
-
901
- parseModuleDefinition() {
902
- const loc = this.peek().loc
903
- this.advance()
904
-
905
- const name = this.parseConstantPath()
906
- this.skipNewlines()
907
- const body = this.parseBody('end')
908
-
909
- return new AST.ModuleDefinition(name, body, loc)
910
- }
911
-
912
- parseConstantPath() {
913
- let path = null
914
-
915
- if (this.match('SCOPE')) {
916
- this.advance()
917
- path = new AST.ScopedConstant(null, this.expect('CONSTANT').value)
918
- } else {
919
- path = new AST.Constant(this.expect('CONSTANT').value)
920
- }
921
-
922
- while (this.match('SCOPE')) {
923
- this.advance()
924
- path = new AST.ScopedConstant(path, this.expect('CONSTANT').value)
925
- }
926
-
927
- return path
928
- }
929
-
930
- parseIfStatement() {
931
- const loc = this.peek().loc
932
- this.advance()
933
-
934
- const test = this.parseExpression()
935
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'then') {
936
- this.advance()
937
- }
938
- this.skipNewlines()
939
-
940
- const consequent = []
941
- while (!this.match('EOF')) {
942
- const token = this.peek()
943
- if (token.type === 'KEYWORD') {
944
- const kw = this.translateKeyword(token.value)
945
- if (kw === 'end' || kw === 'else' || kw === 'elsif') break
946
- }
947
- consequent.push(this.parseStatement())
948
- this.skipNewlines()
949
- }
950
-
951
- let alternate = null
952
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'elsif') {
953
- alternate = this.parseElsifClause()
954
- } else if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'else') {
955
- this.advance()
956
- this.skipNewlines()
957
- alternate = this.parseBody('end')
958
- }
959
-
960
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'end') {
961
- this.advance()
962
- }
963
-
964
- return new AST.IfStatement(test, consequent, alternate, false, loc)
965
- }
966
-
967
- parseElsifClause() {
968
- const loc = this.peek().loc
969
- this.advance()
970
-
971
- const test = this.parseExpression()
972
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'then') {
973
- this.advance()
974
- }
975
- this.skipNewlines()
976
-
977
- const consequent = []
978
- while (!this.match('EOF')) {
979
- const token = this.peek()
980
- if (token.type === 'KEYWORD') {
981
- const kw = this.translateKeyword(token.value)
982
- if (kw === 'end' || kw === 'else' || kw === 'elsif') break
983
- }
984
- consequent.push(this.parseStatement())
985
- this.skipNewlines()
986
- }
987
-
988
- let nextClause = null
989
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'elsif') {
990
- nextClause = this.parseElsifClause()
991
- } else if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'else') {
992
- this.advance()
993
- this.skipNewlines()
994
- nextClause = this.parseBody('end')
995
- }
996
-
997
- return new AST.IfStatement(test, consequent, nextClause, false, loc)
998
- }
999
-
1000
- parseUnlessStatement() {
1001
- const loc = this.peek().loc
1002
- this.advance()
1003
-
1004
- const test = this.parseExpression()
1005
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'then') {
1006
- this.advance()
1007
- }
1008
- this.skipNewlines()
1009
-
1010
- const consequent = []
1011
- while (!this.match('EOF')) {
1012
- const token = this.peek()
1013
- if (token.type === 'KEYWORD') {
1014
- const kw = this.translateKeyword(token.value)
1015
- if (kw === 'end' || kw === 'else') break
1016
- }
1017
- consequent.push(this.parseStatement())
1018
- this.skipNewlines()
1019
- }
1020
-
1021
- let alternate = null
1022
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'else') {
1023
- this.advance()
1024
- this.skipNewlines()
1025
- alternate = this.parseBody('end')
1026
- }
1027
-
1028
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'end') {
1029
- this.advance()
1030
- }
1031
-
1032
- return new AST.UnlessStatement(test, consequent, alternate, false, loc)
1033
- }
1034
-
1035
- parseCaseStatement() {
1036
- const loc = this.peek().loc
1037
- this.advance()
1038
-
1039
- let expression = null
1040
- if (!this.match('NEWLINE', 'KEYWORD')) {
1041
- expression = this.parseExpression()
1042
- }
1043
- this.skipNewlines()
1044
-
1045
- const whens = []
1046
- while (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'when') {
1047
- whens.push(this.parseWhenClause())
1048
- }
1049
-
1050
- let elseBody = null
1051
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'else') {
1052
- this.advance()
1053
- this.skipNewlines()
1054
- elseBody = this.parseBody('end')
1055
- }
1056
-
1057
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'end') {
1058
- this.advance()
1059
- }
1060
-
1061
- return new AST.CaseStatement(expression, whens, elseBody, loc)
1062
- }
1063
-
1064
- parseWhenClause() {
1065
- const loc = this.peek().loc
1066
- this.advance()
1067
-
1068
- const conditions = [this.parseExpression()]
1069
- while (this.match('COMMA')) {
1070
- this.advance()
1071
- conditions.push(this.parseExpression())
1072
- }
1073
-
1074
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'then') {
1075
- this.advance()
1076
- }
1077
- this.skipNewlines()
1078
-
1079
- const body = []
1080
- while (!this.match('EOF')) {
1081
- const token = this.peek()
1082
- if (token.type === 'KEYWORD') {
1083
- const kw = this.translateKeyword(token.value)
1084
- if (kw === 'end' || kw === 'else' || kw === 'when') break
1085
- }
1086
- body.push(this.parseStatement())
1087
- this.skipNewlines()
1088
- }
1089
-
1090
- return new AST.WhenClause(conditions, body, loc)
1091
- }
1092
-
1093
- parseWhileStatement() {
1094
- const loc = this.peek().loc
1095
- this.advance()
1096
-
1097
- const test = this.parseExpression()
1098
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'do') {
1099
- this.advance()
1100
- }
1101
- this.skipNewlines()
1102
-
1103
- const body = this.parseBody('end')
1104
-
1105
- return new AST.WhileStatement(test, body, false, loc)
1106
- }
1107
-
1108
- parseUntilStatement() {
1109
- const loc = this.peek().loc
1110
- this.advance()
1111
-
1112
- const test = this.parseExpression()
1113
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'do') {
1114
- this.advance()
1115
- }
1116
- this.skipNewlines()
1117
-
1118
- const body = this.parseBody('end')
1119
-
1120
- return new AST.UntilStatement(test, body, false, loc)
1121
- }
1122
-
1123
- parseForStatement() {
1124
- const loc = this.peek().loc
1125
- this.advance()
1126
-
1127
- const variable = this.parseAssignmentTarget()
1128
- this.expect('KEYWORD')
1129
- const iterable = this.parseExpression()
1130
-
1131
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'do') {
1132
- this.advance()
1133
- }
1134
- this.skipNewlines()
1135
-
1136
- const body = this.parseBody('end')
1137
-
1138
- return new AST.ForStatement(variable, iterable, body, loc)
1139
- }
1140
-
1141
- parseLoopStatement() {
1142
- const loc = this.peek().loc
1143
- this.advance()
1144
-
1145
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'do') {
1146
- this.advance()
1147
- }
1148
- this.skipNewlines()
1149
-
1150
- const body = this.parseBody('end')
1151
-
1152
- return new AST.LoopStatement(body, loc)
1153
- }
1154
-
1155
- parseBeginBlock() {
1156
- const loc = this.peek().loc
1157
- this.advance()
1158
- this.skipNewlines()
1159
-
1160
- const body = []
1161
- while (!this.match('EOF')) {
1162
- const token = this.peek()
1163
- if (token.type === 'KEYWORD') {
1164
- const kw = this.translateKeyword(token.value)
1165
- if (kw === 'end' || kw === 'rescue' || kw === 'else' || kw === 'ensure') break
1166
- }
1167
- body.push(this.parseStatement())
1168
- this.skipNewlines()
1169
- }
1170
-
1171
- const rescues = []
1172
- while (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'rescue') {
1173
- rescues.push(this.parseRescueClause())
1174
- }
1175
-
1176
- let elseBody = null
1177
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'else') {
1178
- this.advance()
1179
- this.skipNewlines()
1180
- elseBody = []
1181
- while (!this.match('EOF')) {
1182
- const token = this.peek()
1183
- if (token.type === 'KEYWORD') {
1184
- const kw = this.translateKeyword(token.value)
1185
- if (kw === 'end' || kw === 'ensure') break
1186
- }
1187
- elseBody.push(this.parseStatement())
1188
- this.skipNewlines()
1189
- }
1190
- }
1191
-
1192
- let ensure = null
1193
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'ensure') {
1194
- ensure = this.parseEnsureClause()
1195
- }
1196
-
1197
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'end') {
1198
- this.advance()
1199
- }
1200
-
1201
- return new AST.BeginBlock(body, rescues, elseBody, ensure, loc)
1202
- }
1203
-
1204
- parseRescueClause() {
1205
- const loc = this.peek().loc
1206
- this.advance()
1207
-
1208
- const exceptionTypes = []
1209
- let variable = null
1210
-
1211
- if (!this.match('NEWLINE', 'HASH_ROCKET')) {
1212
- exceptionTypes.push(this.parseConstantPath())
1213
- while (this.match('COMMA')) {
1214
- this.advance()
1215
- exceptionTypes.push(this.parseConstantPath())
1216
- }
1217
- }
1218
-
1219
- if (this.match('HASH_ROCKET')) {
1220
- this.advance()
1221
- variable = new AST.Identifier(this.expect('IDENTIFIER').value)
1222
- }
1223
-
1224
- this.skipNewlines()
1225
-
1226
- const body = []
1227
- while (!this.match('EOF')) {
1228
- const token = this.peek()
1229
- if (token.type === 'KEYWORD') {
1230
- const kw = this.translateKeyword(token.value)
1231
- if (kw === 'end' || kw === 'rescue' || kw === 'else' || kw === 'ensure') break
1232
- }
1233
- body.push(this.parseStatement())
1234
- this.skipNewlines()
1235
- }
1236
-
1237
- return new AST.RescueClause(exceptionTypes, variable, body, loc)
1238
- }
1239
-
1240
- parseEnsureClause() {
1241
- const loc = this.peek().loc
1242
- this.advance()
1243
- this.skipNewlines()
1244
-
1245
- const body = []
1246
- while (!this.match('EOF')) {
1247
- const token = this.peek()
1248
- if (token.type === 'KEYWORD' && this.translateKeyword(token.value) === 'end') break
1249
- body.push(this.parseStatement())
1250
- this.skipNewlines()
1251
- }
1252
-
1253
- return new AST.EnsureClause(body, loc)
1254
- }
1255
-
1256
- parseReturnStatement() {
1257
- const loc = this.peek().loc
1258
- this.advance()
1259
-
1260
- let value = null
1261
- if (!this.match('NEWLINE', 'EOF', 'KEYWORD')) {
1262
- value = this.parseExpression()
1263
- }
1264
-
1265
- return new AST.ReturnStatement(value, loc)
1266
- }
1267
-
1268
- parseBreakStatement() {
1269
- const loc = this.peek().loc
1270
- this.advance()
1271
-
1272
- let value = null
1273
- if (!this.match('NEWLINE', 'EOF', 'KEYWORD')) {
1274
- value = this.parseExpression()
1275
- }
1276
-
1277
- return new AST.BreakStatement(value, loc)
1278
- }
1279
-
1280
- parseNextStatement() {
1281
- const loc = this.peek().loc
1282
- this.advance()
1283
-
1284
- let value = null
1285
- if (!this.match('NEWLINE', 'EOF', 'KEYWORD')) {
1286
- value = this.parseExpression()
1287
- }
1288
-
1289
- return new AST.NextStatement(value, loc)
1290
- }
1291
-
1292
- parseRedoStatement() {
1293
- const loc = this.peek().loc
1294
- this.advance()
1295
- return new AST.RedoStatement(loc)
1296
- }
1297
-
1298
- parseRetryStatement() {
1299
- const loc = this.peek().loc
1300
- this.advance()
1301
- return new AST.RetryStatement(loc)
1302
- }
1303
-
1304
- parseRaiseStatement() {
1305
- const loc = this.peek().loc
1306
- this.advance()
1307
-
1308
- let exception = null
1309
- let message = null
1310
-
1311
- if (!this.match('NEWLINE', 'EOF')) {
1312
- exception = this.parseExpression()
1313
- if (this.match('COMMA')) {
1314
- this.advance()
1315
- message = this.parseExpression()
1316
- }
1317
- }
1318
-
1319
- return new AST.RaiseStatement(exception, message, null, loc)
1320
- }
1321
-
1322
- parseRequireStatement() {
1323
- const loc = this.peek().loc
1324
- this.advance()
1325
- const path = this.parseExpression()
1326
- return new AST.RequireStatement(path, false, loc)
1327
- }
1328
-
1329
- parseRequireRelativeStatement() {
1330
- const loc = this.peek().loc
1331
- this.advance()
1332
- const path = this.parseExpression()
1333
- return new AST.RequireStatement(path, true, loc)
1334
- }
1335
-
1336
- parseLoadStatement() {
1337
- const loc = this.peek().loc
1338
- this.advance()
1339
- const path = this.parseExpression()
1340
-
1341
- let wrap = false
1342
- if (this.match('COMMA')) {
1343
- this.advance()
1344
- wrap = this.parseExpression()
1345
- }
1346
-
1347
- return new AST.LoadStatement(path, wrap, loc)
1348
- }
1349
-
1350
- parseIncludeStatement() {
1351
- const loc = this.peek().loc
1352
- this.advance()
1353
-
1354
- const modules = [this.parseConstantPath()]
1355
- while (this.match('COMMA')) {
1356
- this.advance()
1357
- modules.push(this.parseConstantPath())
1358
- }
1359
-
1360
- return new AST.IncludeStatement(modules, loc)
1361
- }
1362
-
1363
- parseExtendStatement() {
1364
- const loc = this.peek().loc
1365
- this.advance()
1366
-
1367
- const modules = [this.parseConstantPath()]
1368
- while (this.match('COMMA')) {
1369
- this.advance()
1370
- modules.push(this.parseConstantPath())
1371
- }
1372
-
1373
- return new AST.ExtendStatement(modules, loc)
1374
- }
1375
-
1376
- parsePrependStatement() {
1377
- const loc = this.peek().loc
1378
- this.advance()
1379
-
1380
- const modules = [this.parseConstantPath()]
1381
- while (this.match('COMMA')) {
1382
- this.advance()
1383
- modules.push(this.parseConstantPath())
1384
- }
1385
-
1386
- return new AST.PrependStatement(modules, loc)
1387
- }
1388
-
1389
- parseAliasStatement() {
1390
- const loc = this.peek().loc
1391
- this.advance()
1392
-
1393
- const newName = this.parseSymbolOrMethod()
1394
- const oldName = this.parseSymbolOrMethod()
1395
-
1396
- return new AST.AliasStatement(newName, oldName, loc)
1397
- }
1398
-
1399
- parseSymbolOrMethod() {
1400
- if (this.match('SYMBOL')) {
1401
- return new AST.Symbol(this.advance().value)
1402
- }
1403
- return new AST.Identifier(this.advance().value)
1404
- }
1405
-
1406
- parseUndefStatement() {
1407
- const loc = this.peek().loc
1408
- this.advance()
1409
-
1410
- const methods = [this.parseSymbolOrMethod()]
1411
- while (this.match('COMMA')) {
1412
- this.advance()
1413
- methods.push(this.parseSymbolOrMethod())
1414
- }
1415
-
1416
- return new AST.UndefStatement(methods, loc)
1417
- }
1418
-
1419
- parseVisibilityStatement() {
1420
- const loc = this.peek().loc
1421
- const visibility = this.translateKeyword(this.advance().value)
1422
-
1423
- if (this.match('NEWLINE', 'EOF')) {
1424
- return new AST.MethodCall(null, visibility, [], null, false, loc)
1425
- }
1426
-
1427
- const methods = [this.parseSymbolOrMethod()]
1428
- while (this.match('COMMA')) {
1429
- this.advance()
1430
- methods.push(this.parseSymbolOrMethod())
1431
- }
1432
-
1433
- return new AST.MethodCall(null, visibility, methods, null, false, loc)
1434
- }
1435
-
1436
- parseAttrAccessor() {
1437
- const loc = this.peek().loc
1438
- const type = this.translateKeyword(this.advance().value)
1439
-
1440
- const attributes = []
1441
- attributes.push(this.parseSymbolOrMethod())
1442
- while (this.match('COMMA')) {
1443
- this.advance()
1444
- attributes.push(this.parseSymbolOrMethod())
1445
- }
1446
-
1447
- return new AST.AttrAccessor(type, attributes, loc)
1448
- }
1449
-
1450
- parsePrintStatement() {
1451
- const loc = this.peek().loc
1452
- const method = this.translateKeyword(this.advance().value)
1453
-
1454
- const args = []
1455
- if (!this.match('NEWLINE', 'EOF')) {
1456
- args.push(this.parseExpression())
1457
- while (this.match('COMMA')) {
1458
- this.advance()
1459
- args.push(this.parseExpression())
1460
- }
1461
- }
1462
-
1463
- return new AST.MethodCall(null, method, args, null, false, loc)
1464
- }
1465
-
1466
- parseBody(endKeyword) {
1467
- const body = []
1468
-
1469
- while (!this.match('EOF')) {
1470
- const token = this.peek()
1471
- if (token.type === 'KEYWORD' && this.translateKeyword(token.value) === endKeyword) {
1472
- this.advance()
1473
- break
1474
- }
1475
- body.push(this.parseStatement())
1476
- this.skipNewlines()
1477
- }
1478
-
1479
- return body
1480
- }
1481
-
1482
- parseExpression() {
1483
- return this.parseAssignment()
1484
- }
1485
-
1486
- parseAssignment() {
1487
- const left = this.parseTernary()
1488
-
1489
- if (this.match('ASSIGN', 'PLUS_ASSIGN', 'MINUS_ASSIGN', 'TIMES_ASSIGN', 'DIV_ASSIGN', 'MOD_ASSIGN',
1490
- 'POW_ASSIGN', 'AND_ASSIGN', 'OR_ASSIGN', 'BIT_AND_ASSIGN', 'BIT_OR_ASSIGN', 'BIT_XOR_ASSIGN',
1491
- 'LSHIFT_ASSIGN', 'RSHIFT_ASSIGN')) {
1492
- const op = this.advance()
1493
- const right = this.parseAssignment()
1494
- return new AST.AssignmentExpression(op.value, left, right, left.loc)
1495
- }
1496
-
1497
- return left
1498
- }
1499
-
1500
- parseTernary() {
1501
- const test = this.parseOr()
1502
-
1503
- if (this.match('QUESTION')) {
1504
- this.advance()
1505
- const consequent = this.parseExpression()
1506
- this.expect('COLON')
1507
- const alternate = this.parseExpression()
1508
- return new AST.ConditionalExpression(test, consequent, alternate, test.loc)
1509
- }
1510
-
1511
- return test
1512
- }
1513
-
1514
- parseOr() {
1515
- let left = this.parseAnd()
1516
-
1517
- while (this.match('OR') || (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'or')) {
1518
- this.advance()
1519
- const right = this.parseAnd()
1520
- left = new AST.LogicalExpression('||', left, right, left.loc)
1521
- }
1522
-
1523
- return left
1524
- }
1525
-
1526
- parseAnd() {
1527
- let left = this.parseNot()
1528
-
1529
- while (this.match('AND') || (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'and')) {
1530
- this.advance()
1531
- const right = this.parseNot()
1532
- left = new AST.LogicalExpression('&&', left, right, left.loc)
1533
- }
1534
-
1535
- return left
1536
- }
1537
-
1538
- parseNot() {
1539
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'not') {
1540
- const loc = this.peek().loc
1541
- this.advance()
1542
- return new AST.NotExpression(this.parseNot(), loc)
1543
- }
1544
-
1545
- return this.parseEquality()
1546
- }
1547
-
1548
- parseEquality() {
1549
- let left = this.parseComparison()
1550
-
1551
- while (this.match('EQUAL', 'NOT_EQUAL', 'CASE_EQUAL', 'MATCH', 'NOT_MATCH')) {
1552
- const op = this.advance()
1553
- const right = this.parseComparison()
1554
- if (op.type === 'MATCH') {
1555
- left = new AST.MatchExpression(left, right, left.loc)
1556
- } else if (op.type === 'NOT_MATCH') {
1557
- left = new AST.NotMatchExpression(left, right, left.loc)
1558
- } else {
1559
- left = new AST.BinaryExpression(op.value, left, right, left.loc)
1560
- }
1561
- }
1562
-
1563
- return left
1564
- }
1565
-
1566
- parseComparison() {
1567
- let left = this.parseBitwiseOr()
1568
-
1569
- while (this.match('LT', 'GT', 'LTE', 'GTE', 'SPACESHIP')) {
1570
- const op = this.advance()
1571
- const right = this.parseBitwiseOr()
1572
- left = new AST.BinaryExpression(op.value, left, right, left.loc)
1573
- }
1574
-
1575
- return left
1576
- }
1577
-
1578
- parseBitwiseOr() {
1579
- let left = this.parseBitwiseXor()
1580
-
1581
- while (this.match('PIPE')) {
1582
- this.advance()
1583
- const right = this.parseBitwiseXor()
1584
- left = new AST.BinaryExpression('|', left, right, left.loc)
1585
- }
1586
-
1587
- return left
1588
- }
1589
-
1590
- parseBitwiseXor() {
1591
- let left = this.parseBitwiseAnd()
1592
-
1593
- while (this.match('CARET')) {
1594
- this.advance()
1595
- const right = this.parseBitwiseAnd()
1596
- left = new AST.BinaryExpression('^', left, right, left.loc)
1597
- }
1598
-
1599
- return left
1600
- }
1601
-
1602
- parseBitwiseAnd() {
1603
- let left = this.parseShift()
1604
-
1605
- while (this.match('AMPERSAND') && !this.isBlockArg()) {
1606
- this.advance()
1607
- const right = this.parseShift()
1608
- left = new AST.BinaryExpression('&', left, right, left.loc)
1609
- }
1610
-
1611
- return left
1612
- }
1613
-
1614
- isBlockArg() {
1615
- return this.peek(1).type === 'IDENTIFIER' || this.peek(1).type === 'COLON'
1616
- }
1617
-
1618
- parseShift() {
1619
- let left = this.parseRange()
1620
-
1621
- while (this.match('LSHIFT', 'RSHIFT')) {
1622
- const op = this.advance()
1623
- const right = this.parseRange()
1624
- left = new AST.BinaryExpression(op.value, left, right, left.loc)
1625
- }
1626
-
1627
- return left
1628
- }
1629
-
1630
- parseRange() {
1631
- let left = this.parseAdditive()
1632
-
1633
- if (this.match('INCLUSIVE_RANGE', 'EXCLUSIVE_RANGE')) {
1634
- const exclusive = this.advance().type === 'EXCLUSIVE_RANGE'
1635
- const right = this.parseAdditive()
1636
- return new AST.RangeLiteral(left, right, exclusive, left.loc)
1637
- }
1638
-
1639
- return left
1640
- }
1641
-
1642
- parseAdditive() {
1643
- let left = this.parseMultiplicative()
1644
-
1645
- while (this.match('PLUS', 'MINUS')) {
1646
- const op = this.advance()
1647
- const right = this.parseMultiplicative()
1648
- left = new AST.BinaryExpression(op.value, left, right, left.loc)
1649
- }
1650
-
1651
- return left
1652
- }
1653
-
1654
- parseMultiplicative() {
1655
- let left = this.parseExponential()
1656
-
1657
- while (this.match('TIMES', 'DIV', 'MOD')) {
1658
- const op = this.advance()
1659
- const right = this.parseExponential()
1660
- left = new AST.BinaryExpression(op.value, left, right, left.loc)
1661
- }
1662
-
1663
- return left
1664
- }
1665
-
1666
- parseExponential() {
1667
- const left = this.parseUnary()
1668
-
1669
- if (this.match('POW')) {
1670
- this.advance()
1671
- const right = this.parseExponential()
1672
- return new AST.BinaryExpression('**', left, right, left.loc)
1673
- }
1674
-
1675
- return left
1676
- }
1677
-
1678
- parseUnary() {
1679
- const loc = this.peek().loc
1680
-
1681
- if (this.match('NOT')) {
1682
- this.advance()
1683
- return new AST.UnaryExpression('!', this.parseUnary(), true, loc)
1684
- }
1685
-
1686
- if (this.match('TILDE')) {
1687
- this.advance()
1688
- return new AST.UnaryExpression('~', this.parseUnary(), true, loc)
1689
- }
1690
-
1691
- if (this.match('PLUS')) {
1692
- this.advance()
1693
- return new AST.UnaryExpression('+', this.parseUnary(), true, loc)
1694
- }
1695
-
1696
- if (this.match('MINUS')) {
1697
- this.advance()
1698
- return new AST.UnaryExpression('-', this.parseUnary(), true, loc)
1699
- }
1700
-
1701
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'defined?') {
1702
- this.advance()
1703
- const arg = this.match('LPAREN') ? (this.advance(), this.parseExpression(), this.expect('RPAREN'), this.parseExpression()) : this.parseUnary()
1704
- return new AST.DefinedExpression(arg, loc)
1705
- }
1706
-
1707
- return this.parseCall()
1708
- }
1709
-
1710
- parseCall() {
1711
- let expr = this.parsePrimary()
1712
-
1713
- while (true) {
1714
- if (this.match('DOT')) {
1715
- this.advance()
1716
- const method = this.advance().value
1717
- expr = this.parseMethodCall(expr, method, false)
1718
- } else if (this.match('SAFE_NAV')) {
1719
- this.advance()
1720
- const method = this.advance().value
1721
- expr = this.parseMethodCall(expr, method, true)
1722
- } else if (this.match('SCOPE')) {
1723
- this.advance()
1724
- if (this.match('CONSTANT')) {
1725
- expr = new AST.ScopedConstant(expr, this.advance().value)
1726
- } else {
1727
- const method = this.advance().value
1728
- expr = this.parseMethodCall(expr, method, false)
1729
- }
1730
- } else if (this.match('LBRACKET')) {
1731
- this.advance()
1732
- const index = this.parseExpression()
1733
- this.expect('RBRACKET')
1734
-
1735
- if (this.match('ASSIGN')) {
1736
- this.advance()
1737
- const value = this.parseExpression()
1738
- expr = new AST.IndexAssignment(expr, index, value, expr.loc)
1739
- } else {
1740
- expr = new AST.IndexAccess(expr, index, expr.loc)
1741
- }
1742
- } else if (this.match('LPAREN')) {
1743
- expr = this.parseMethodCall(null, expr, false)
1744
- } else {
1745
- break
1746
- }
1747
- }
1748
-
1749
- return expr
1750
- }
1751
-
1752
- parseMethodCall(receiver, method, safeNavigation) {
1753
- const loc = (receiver || method).loc || this.peek().loc
1754
- const args = []
1755
- let block = null
1756
-
1757
- if (this.match('LPAREN')) {
1758
- this.advance()
1759
- while (!this.match('RPAREN', 'EOF')) {
1760
- if (this.match('TIMES')) {
1761
- this.advance()
1762
- args.push(new AST.SplatExpression(this.parseExpression()))
1763
- } else if (this.match('POW')) {
1764
- this.advance()
1765
- args.push(new AST.DoubleSplatExpression(this.parseExpression()))
1766
- } else if (this.match('AMPERSAND')) {
1767
- this.advance()
1768
- args.push(new AST.SplatExpression(this.parseExpression()))
1769
- } else {
1770
- args.push(this.parseExpression())
1771
- }
1772
- if (this.match('COMMA')) this.advance()
1773
- }
1774
- this.expect('RPAREN')
1775
- } else if (!this.match('NEWLINE', 'EOF', 'KEYWORD', 'LBRACE', 'DOT', 'SAFE_NAV') && !this.peek().value?.match(/^(do|end|if|unless|while|until|rescue)$/)) {
1776
- while (!this.match('NEWLINE', 'EOF', 'LBRACE') && !this.isBlockKeyword()) {
1777
- if (this.match('TIMES')) {
1778
- this.advance()
1779
- args.push(new AST.SplatExpression(this.parseExpression()))
1780
- } else if (this.match('POW')) {
1781
- this.advance()
1782
- args.push(new AST.DoubleSplatExpression(this.parseExpression()))
1783
- } else if (this.match('AMPERSAND')) {
1784
- this.advance()
1785
- args.push(new AST.SplatExpression(this.parseExpression()))
1786
- } else {
1787
- args.push(this.parseExpression())
1788
- }
1789
- if (this.match('COMMA')) this.advance()
1790
- else break
1791
- }
1792
- }
1793
-
1794
- if (this.match('LBRACE') || this.isBlockKeyword()) {
1795
- block = this.parseBlock()
1796
- }
1797
-
1798
- const methodName = typeof method === 'string' ? method : (method.name || method.value || method)
1799
- return new AST.MethodCall(receiver, methodName, args, block, safeNavigation, loc)
1800
- }
1801
-
1802
- isBlockKeyword() {
1803
- return this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'do'
1804
- }
1805
-
1806
- parseBlock() {
1807
- const loc = this.peek().loc
1808
- let useBraces = false
1809
-
1810
- if (this.match('LBRACE')) {
1811
- useBraces = true
1812
- this.advance()
1813
- } else if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'do') {
1814
- this.advance()
1815
- }
1816
-
1817
- const parameters = []
1818
- if (this.match('PIPE')) {
1819
- this.advance()
1820
- while (!this.match('PIPE', 'EOF')) {
1821
- parameters.push(this.parseBlockParameter())
1822
- if (this.match('COMMA')) this.advance()
1823
- }
1824
- this.expect('PIPE')
1825
- }
1826
-
1827
- this.skipNewlines()
1828
-
1829
- const body = []
1830
- if (useBraces) {
1831
- while (!this.match('RBRACE', 'EOF')) {
1832
- body.push(this.parseStatement())
1833
- this.skipNewlines()
1834
- }
1835
- this.expect('RBRACE')
1836
- } else {
1837
- while (!this.match('EOF')) {
1838
- const token = this.peek()
1839
- if (token.type === 'KEYWORD' && this.translateKeyword(token.value) === 'end') {
1840
- this.advance()
1841
- break
1842
- }
1843
- body.push(this.parseStatement())
1844
- this.skipNewlines()
1845
- }
1846
- }
1847
-
1848
- return new AST.Block(parameters, body, loc)
1849
- }
1850
-
1851
- parseBlockParameter() {
1852
- const loc = this.peek().loc
1853
- let splat = false
1854
- let doubleSplat = false
1855
- let blockArg = false
1856
-
1857
- if (this.match('TIMES')) {
1858
- splat = true
1859
- this.advance()
1860
- } else if (this.match('POW')) {
1861
- doubleSplat = true
1862
- this.advance()
1863
- } else if (this.match('AMPERSAND')) {
1864
- blockArg = true
1865
- this.advance()
1866
- }
1867
-
1868
- const name = this.expect('IDENTIFIER').value
1869
-
1870
- let defaultValue = null
1871
- if (this.match('ASSIGN')) {
1872
- this.advance()
1873
- defaultValue = this.parseExpression()
1874
- }
1875
-
1876
- return new AST.BlockParameter(name, defaultValue, splat, doubleSplat, blockArg, loc)
1877
- }
1878
-
1879
- parsePrimary() {
1880
- const token = this.peek()
1881
- const loc = token.loc
1882
-
1883
- if (token.type === 'NUMBER') {
1884
- this.advance()
1885
- return new AST.NumericLiteral(token.value, token.raw, loc)
1886
- }
1887
-
1888
- if (token.type === 'FLOAT') {
1889
- this.advance()
1890
- return new AST.FloatLiteral(token.value, token.raw, loc)
1891
- }
1892
-
1893
- if (token.type === 'RATIONAL') {
1894
- this.advance()
1895
- return new AST.RationalLiteral(token.value, token.raw, loc)
1896
- }
1897
-
1898
- if (token.type === 'COMPLEX') {
1899
- this.advance()
1900
- return new AST.ComplexLiteral(token.value, token.raw, loc)
1901
- }
1902
-
1903
- if (token.type === 'STRING') {
1904
- this.advance()
1905
- return new AST.StringLiteral(token.value, token.raw, false, loc)
1906
- }
1907
-
1908
- if (token.type === 'INTERPOLATED_STRING') {
1909
- this.advance()
1910
- const parts = token.parts.map(p => {
1911
- if (p.type === 'string') {
1912
- return new AST.StringPart(p.value)
1913
- } else {
1914
- const subParser = new RubyParser()
1915
- const subAst = subParser.parse(p.value)
1916
- return new AST.InterpolationPart(subAst.body[0]?.expression || subAst.body[0])
1917
- }
1918
- })
1919
- return new AST.InterpolatedString(parts, loc)
1920
- }
1921
-
1922
- if (token.type === 'SYMBOL') {
1923
- this.advance()
1924
- return new AST.Symbol(token.value, loc)
1925
- }
1926
-
1927
- if (token.type === 'REGEXP') {
1928
- this.advance()
1929
- return new AST.RegexpLiteral(token.pattern, token.flags, loc)
1930
- }
1931
-
1932
- if (token.type === 'PERCENT_LITERAL') {
1933
- this.advance()
1934
- return new AST.PercentArray(token.elements, token.literalType, loc)
1935
- }
1936
-
1937
- if (token.type === 'BACKTICK') {
1938
- this.advance()
1939
- return new AST.BacktickLiteral(token.value, loc)
1940
- }
1941
-
1942
- if (token.type === 'INSTANCE_VAR') {
1943
- this.advance()
1944
- return new AST.InstanceVariable(token.value, loc)
1945
- }
1946
-
1947
- if (token.type === 'CLASS_VAR') {
1948
- this.advance()
1949
- return new AST.ClassVariable(token.value, loc)
1950
- }
1951
-
1952
- if (token.type === 'GLOBAL_VAR') {
1953
- this.advance()
1954
- return new AST.GlobalVariable(token.value, loc)
1955
- }
1956
-
1957
- if (token.type === 'CONSTANT') {
1958
- return this.parseConstantPath()
1959
- }
1960
-
1961
- if (token.type === 'IDENTIFIER' || token.type === 'METHOD') {
1962
- this.advance()
1963
- const name = token.value
1964
-
1965
- if (this.match('LPAREN', 'LBRACE') || this.isBlockKeyword() || (!this.match('NEWLINE', 'EOF', 'DOT', 'ASSIGN') && this.peek().type === 'IDENTIFIER')) {
1966
- return this.parseMethodCall(null, name, false)
1967
- }
1968
-
1969
- return new AST.Identifier(name, loc)
1970
- }
1971
-
1972
- if (token.type === 'KEYWORD') {
1973
- const kw = this.translateKeyword(token.value)
1974
-
1975
- if (kw === 'true') {
1976
- this.advance()
1977
- return new AST.BooleanLiteral(true, loc)
1978
- }
1979
- if (kw === 'false') {
1980
- this.advance()
1981
- return new AST.BooleanLiteral(false, loc)
1982
- }
1983
- if (kw === 'nil') {
1984
- this.advance()
1985
- return new AST.NilLiteral(loc)
1986
- }
1987
- if (kw === 'self') {
1988
- this.advance()
1989
- return new AST.SelfExpression(loc)
1990
- }
1991
- if (kw === 'super') {
1992
- this.advance()
1993
- return this.parseSuperCall(loc)
1994
- }
1995
- if (kw === 'yield') {
1996
- this.advance()
1997
- return this.parseYieldExpression(loc)
1998
- }
1999
- if (kw === 'lambda') {
2000
- this.advance()
2001
- return this.parseLambda(loc)
2002
- }
2003
- if (kw === 'proc') {
2004
- this.advance()
2005
- return this.parseProc(loc)
2006
- }
2007
- if (kw === '__FILE__') {
2008
- this.advance()
2009
- return new AST.File(loc)
2010
- }
2011
- if (kw === '__LINE__') {
2012
- this.advance()
2013
- return new AST.Line(loc)
2014
- }
2015
- if (kw === '__ENCODING__') {
2016
- this.advance()
2017
- return new AST.Encoding(loc)
2018
- }
2019
- }
2020
-
2021
- if (this.match('LBRACKET')) {
2022
- return this.parseArrayLiteral()
2023
- }
2024
-
2025
- if (this.match('LBRACE')) {
2026
- return this.parseHashLiteral()
2027
- }
2028
-
2029
- if (this.match('LPAREN')) {
2030
- this.advance()
2031
- const expr = this.parseExpression()
2032
- this.expect('RPAREN')
2033
- return new AST.ParenthesizedExpression(expr, loc)
2034
- }
2035
-
2036
- if (this.match('LAMBDA_ARROW')) {
2037
- return this.parseStabbyLambda()
2038
- }
2039
-
2040
- throw new Error(`Token inattendu ${token.type} à ligne ${loc?.line}`)
2041
- }
2042
-
2043
- parseSuperCall(loc) {
2044
- let args = null
2045
- let block = null
2046
-
2047
- if (this.match('LPAREN')) {
2048
- this.advance()
2049
- args = []
2050
- while (!this.match('RPAREN', 'EOF')) {
2051
- args.push(this.parseExpression())
2052
- if (this.match('COMMA')) this.advance()
2053
- }
2054
- this.expect('RPAREN')
2055
- }
2056
-
2057
- if (this.match('LBRACE') || this.isBlockKeyword()) {
2058
- block = this.parseBlock()
2059
- }
2060
-
2061
- return new AST.SuperCall(args, block, loc)
2062
- }
2063
-
2064
- parseYieldExpression(loc) {
2065
- const args = []
2066
-
2067
- if (this.match('LPAREN')) {
2068
- this.advance()
2069
- while (!this.match('RPAREN', 'EOF')) {
2070
- args.push(this.parseExpression())
2071
- if (this.match('COMMA')) this.advance()
2072
- }
2073
- this.expect('RPAREN')
2074
- } else if (!this.match('NEWLINE', 'EOF')) {
2075
- args.push(this.parseExpression())
2076
- while (this.match('COMMA')) {
2077
- this.advance()
2078
- args.push(this.parseExpression())
2079
- }
2080
- }
2081
-
2082
- return new AST.YieldExpression(args, loc)
2083
- }
2084
-
2085
- parseLambda(loc) {
2086
- const block = this.parseBlock()
2087
- return new AST.LambdaLiteral(block.parameters, block.body, loc)
2088
- }
2089
-
2090
- parseProc(loc) {
2091
- const block = this.parseBlock()
2092
- return new AST.ProcLiteral(block.parameters, block.body, loc)
2093
- }
2094
-
2095
- parseStabbyLambda() {
2096
- const loc = this.peek().loc
2097
- this.advance()
2098
-
2099
- const parameters = []
2100
- if (this.match('LPAREN')) {
2101
- this.advance()
2102
- while (!this.match('RPAREN', 'EOF')) {
2103
- parameters.push(this.parseMethodParameter())
2104
- if (this.match('COMMA')) this.advance()
2105
- }
2106
- this.expect('RPAREN')
2107
- }
2108
-
2109
- const body = []
2110
- if (this.match('LBRACE')) {
2111
- this.advance()
2112
- while (!this.match('RBRACE', 'EOF')) {
2113
- body.push(this.parseStatement())
2114
- this.skipNewlines()
2115
- }
2116
- this.expect('RBRACE')
2117
- } else if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'do') {
2118
- this.advance()
2119
- this.skipNewlines()
2120
- while (!this.match('EOF')) {
2121
- if (this.match('KEYWORD') && this.translateKeyword(this.peek().value) === 'end') {
2122
- this.advance()
2123
- break
2124
- }
2125
- body.push(this.parseStatement())
2126
- this.skipNewlines()
2127
- }
2128
- }
2129
-
2130
- return new AST.LambdaLiteral(parameters, body, loc)
2131
- }
2132
-
2133
- parseArrayLiteral() {
2134
- const loc = this.peek().loc
2135
- this.expect('LBRACKET')
2136
- const elements = []
2137
-
2138
- while (!this.match('RBRACKET', 'EOF')) {
2139
- this.skipNewlines()
2140
- if (this.match('TIMES')) {
2141
- this.advance()
2142
- elements.push(new AST.SplatExpression(this.parseExpression()))
2143
- } else {
2144
- elements.push(this.parseExpression())
2145
- }
2146
- this.skipNewlines()
2147
- if (this.match('COMMA')) this.advance()
2148
- }
2149
-
2150
- this.expect('RBRACKET')
2151
- return new AST.ArrayLiteral(elements, loc)
2152
- }
2153
-
2154
- parseHashLiteral() {
2155
- const loc = this.peek().loc
2156
- this.expect('LBRACE')
2157
- const pairs = []
2158
-
2159
- while (!this.match('RBRACE', 'EOF')) {
2160
- this.skipNewlines()
2161
-
2162
- if (this.match('POW')) {
2163
- this.advance()
2164
- pairs.push(new AST.DoubleSplatExpression(this.parseExpression()))
2165
- } else {
2166
- let key
2167
- if (this.match('IDENTIFIER', 'METHOD', 'KEYWORD') && this.peek(1).type === 'COLON') {
2168
- key = new AST.Symbol(this.advance().value)
2169
- this.advance()
2170
- } else {
2171
- key = this.parseExpression()
2172
- if (this.match('HASH_ROCKET')) {
2173
- this.advance()
2174
- } else if (this.match('COLON')) {
2175
- this.advance()
2176
- }
2177
- }
2178
- const value = this.parseExpression()
2179
- pairs.push(new AST.HashPair(key, value))
2180
- }
2181
-
2182
- this.skipNewlines()
2183
- if (this.match('COMMA')) this.advance()
2184
- }
2185
-
2186
- this.expect('RBRACE')
2187
- return new AST.HashLiteral(pairs, loc)
2188
- }
2189
-
2190
- parseAssignmentTarget() {
2191
- if (this.match('IDENTIFIER')) {
2192
- return new AST.Identifier(this.advance().value)
2193
- }
2194
- if (this.match('INSTANCE_VAR')) {
2195
- return new AST.InstanceVariable(this.advance().value)
2196
- }
2197
- if (this.match('CLASS_VAR')) {
2198
- return new AST.ClassVariable(this.advance().value)
2199
- }
2200
- if (this.match('GLOBAL_VAR')) {
2201
- return new AST.GlobalVariable(this.advance().value)
2202
- }
2203
- if (this.match('CONSTANT')) {
2204
- return this.parseConstantPath()
2205
- }
2206
-
2207
- return this.parseExpression()
2208
- }
2209
- }
2210
-
2211
- class RubyCodeGenerator {
2212
- constructor(options = {}) {
2213
- this.indentStr = options.indentStr || ' '
2214
- this.indentLevel = 0
2215
- }
2216
-
2217
- indent() {
2218
- return this.indentStr.repeat(this.indentLevel)
2219
- }
2220
-
2221
- generate(ast) {
2222
- if (!ast) return ''
2223
-
2224
- const method = `generate${ast.type}`
2225
- if (this[method]) {
2226
- return this[method](ast)
2227
- }
2228
-
2229
- return ''
2230
- }
2231
-
2232
- generateProgram(node) {
2233
- return node.body.map(stmt => this.generate(stmt)).join('\n')
2234
- }
2235
-
2236
- generateMethodDefinition(node) {
2237
- let result = this.indent()
2238
- result += 'def '
2239
- if (node.singleton) {
2240
- result += this.generate(node.singleton) + '.'
2241
- }
2242
- result += node.name
2243
- if (node.parameters.length) {
2244
- result += '(' + node.parameters.map(p => this.generate(p)).join(', ') + ')'
2245
- }
2246
- result += '\n'
2247
- this.indentLevel++
2248
- result += node.body.map(s => this.generate(s)).join('\n')
2249
- this.indentLevel--
2250
- result += '\n' + this.indent() + 'end'
2251
- return result
2252
- }
2253
-
2254
- generateMethodParameter(node) {
2255
- let result = ''
2256
- if (node.splat) result += '*'
2257
- if (node.doubleSplat) result += '**'
2258
- if (node.blockArg) result += '&'
2259
- result += node.name
2260
- if (node.keyword) result += ':'
2261
- if (node.defaultValue) result += ' = ' + this.generate(node.defaultValue)
2262
- return result
2263
- }
2264
-
2265
- generateClassDefinition(node) {
2266
- let result = this.indent() + 'class ' + this.generate(node.name)
2267
- if (node.superclass) result += ' < ' + this.generate(node.superclass)
2268
- result += '\n'
2269
- this.indentLevel++
2270
- result += node.body.map(s => this.generate(s)).join('\n')
2271
- this.indentLevel--
2272
- result += '\n' + this.indent() + 'end'
2273
- return result
2274
- }
2275
-
2276
- generateSingletonClass(node) {
2277
- let result = this.indent() + 'class << ' + this.generate(node.object) + '\n'
2278
- this.indentLevel++
2279
- result += node.body.map(s => this.generate(s)).join('\n')
2280
- this.indentLevel--
2281
- result += '\n' + this.indent() + 'end'
2282
- return result
2283
- }
2284
-
2285
- generateModuleDefinition(node) {
2286
- let result = this.indent() + 'module ' + this.generate(node.name) + '\n'
2287
- this.indentLevel++
2288
- result += node.body.map(s => this.generate(s)).join('\n')
2289
- this.indentLevel--
2290
- result += '\n' + this.indent() + 'end'
2291
- return result
2292
- }
2293
-
2294
- generateConstant(node) { return node.name }
2295
- generateScopedConstant(node) {
2296
- if (node.scope) return this.generate(node.scope) + '::' + node.name
2297
- return '::' + node.name
2298
- }
2299
-
2300
- generateIfStatement(node) {
2301
- if (node.modifier) {
2302
- return this.indent() + this.generate(node.consequent[0]) + ' if ' + this.generate(node.test)
2303
- }
2304
- let result = this.indent() + 'if ' + this.generate(node.test) + '\n'
2305
- this.indentLevel++
2306
- result += node.consequent.map(s => this.generate(s)).join('\n')
2307
- this.indentLevel--
2308
- if (node.alternate) {
2309
- if (node.alternate.type === 'IfStatement') {
2310
- result += '\n' + this.indent() + 'els' + this.generate(node.alternate).trim()
2311
- } else if (Array.isArray(node.alternate)) {
2312
- result += '\n' + this.indent() + 'else\n'
2313
- this.indentLevel++
2314
- result += node.alternate.map(s => this.generate(s)).join('\n')
2315
- this.indentLevel--
2316
- }
2317
- }
2318
- result += '\n' + this.indent() + 'end'
2319
- return result
2320
- }
2321
-
2322
- generateUnlessStatement(node) {
2323
- if (node.modifier) {
2324
- return this.indent() + this.generate(node.consequent[0]) + ' unless ' + this.generate(node.test)
2325
- }
2326
- let result = this.indent() + 'unless ' + this.generate(node.test) + '\n'
2327
- this.indentLevel++
2328
- result += node.consequent.map(s => this.generate(s)).join('\n')
2329
- this.indentLevel--
2330
- if (node.alternate) {
2331
- result += '\n' + this.indent() + 'else\n'
2332
- this.indentLevel++
2333
- result += node.alternate.map(s => this.generate(s)).join('\n')
2334
- this.indentLevel--
2335
- }
2336
- result += '\n' + this.indent() + 'end'
2337
- return result
2338
- }
2339
-
2340
- generateCaseStatement(node) {
2341
- let result = this.indent() + 'case'
2342
- if (node.expression) result += ' ' + this.generate(node.expression)
2343
- result += '\n'
2344
- result += node.whens.map(w => this.generate(w)).join('\n')
2345
- if (node.elseBody) {
2346
- result += '\n' + this.indent() + 'else\n'
2347
- this.indentLevel++
2348
- result += node.elseBody.map(s => this.generate(s)).join('\n')
2349
- this.indentLevel--
2350
- }
2351
- result += '\n' + this.indent() + 'end'
2352
- return result
2353
- }
2354
-
2355
- generateWhenClause(node) {
2356
- let result = this.indent() + 'when ' + node.conditions.map(c => this.generate(c)).join(', ') + '\n'
2357
- this.indentLevel++
2358
- result += node.body.map(s => this.generate(s)).join('\n')
2359
- this.indentLevel--
2360
- return result
2361
- }
2362
-
2363
- generateWhileStatement(node) {
2364
- if (node.modifier) {
2365
- return this.indent() + this.generate(node.body[0]) + ' while ' + this.generate(node.test)
2366
- }
2367
- let result = this.indent() + 'while ' + this.generate(node.test) + '\n'
2368
- this.indentLevel++
2369
- result += node.body.map(s => this.generate(s)).join('\n')
2370
- this.indentLevel--
2371
- result += '\n' + this.indent() + 'end'
2372
- return result
2373
- }
2374
-
2375
- generateUntilStatement(node) {
2376
- if (node.modifier) {
2377
- return this.indent() + this.generate(node.body[0]) + ' until ' + this.generate(node.test)
2378
- }
2379
- let result = this.indent() + 'until ' + this.generate(node.test) + '\n'
2380
- this.indentLevel++
2381
- result += node.body.map(s => this.generate(s)).join('\n')
2382
- this.indentLevel--
2383
- result += '\n' + this.indent() + 'end'
2384
- return result
2385
- }
2386
-
2387
- generateForStatement(node) {
2388
- let result = this.indent() + 'for ' + this.generate(node.variable) + ' in ' + this.generate(node.iterable) + '\n'
2389
- this.indentLevel++
2390
- result += node.body.map(s => this.generate(s)).join('\n')
2391
- this.indentLevel--
2392
- result += '\n' + this.indent() + 'end'
2393
- return result
2394
- }
2395
-
2396
- generateLoopStatement(node) {
2397
- let result = this.indent() + 'loop do\n'
2398
- this.indentLevel++
2399
- result += node.body.map(s => this.generate(s)).join('\n')
2400
- this.indentLevel--
2401
- result += '\n' + this.indent() + 'end'
2402
- return result
2403
- }
2404
-
2405
- generateBeginBlock(node) {
2406
- let result = this.indent() + 'begin\n'
2407
- this.indentLevel++
2408
- result += node.body.map(s => this.generate(s)).join('\n')
2409
- this.indentLevel--
2410
- result += node.rescues.map(r => '\n' + this.generate(r)).join('')
2411
- if (node.elseBody) {
2412
- result += '\n' + this.indent() + 'else\n'
2413
- this.indentLevel++
2414
- result += node.elseBody.map(s => this.generate(s)).join('\n')
2415
- this.indentLevel--
2416
- }
2417
- if (node.ensure) {
2418
- result += '\n' + this.generate(node.ensure)
2419
- }
2420
- result += '\n' + this.indent() + 'end'
2421
- return result
2422
- }
2423
-
2424
- generateRescueClause(node) {
2425
- let result = this.indent() + 'rescue'
2426
- if (node.exceptionTypes.length) {
2427
- result += ' ' + node.exceptionTypes.map(e => this.generate(e)).join(', ')
2428
- }
2429
- if (node.variable) result += ' => ' + this.generate(node.variable)
2430
- result += '\n'
2431
- this.indentLevel++
2432
- result += node.body.map(s => this.generate(s)).join('\n')
2433
- this.indentLevel--
2434
- return result
2435
- }
2436
-
2437
- generateEnsureClause(node) {
2438
- let result = this.indent() + 'ensure\n'
2439
- this.indentLevel++
2440
- result += node.body.map(s => this.generate(s)).join('\n')
2441
- this.indentLevel--
2442
- return result
2443
- }
2444
-
2445
- generateReturnStatement(node) {
2446
- return this.indent() + 'return' + (node.value ? ' ' + this.generate(node.value) : '')
2447
- }
2448
- generateBreakStatement(node) {
2449
- return this.indent() + 'break' + (node.value ? ' ' + this.generate(node.value) : '')
2450
- }
2451
- generateNextStatement(node) {
2452
- return this.indent() + 'next' + (node.value ? ' ' + this.generate(node.value) : '')
2453
- }
2454
- generateRedoStatement() { return this.indent() + 'redo' }
2455
- generateRetryStatement() { return this.indent() + 'retry' }
2456
-
2457
- generateRaiseStatement(node) {
2458
- let result = this.indent() + 'raise'
2459
- if (node.exception) result += ' ' + this.generate(node.exception)
2460
- if (node.message) result += ', ' + this.generate(node.message)
2461
- return result
2462
- }
2463
-
2464
- generateRequireStatement(node) {
2465
- return this.indent() + (node.relative ? 'require_relative' : 'require') + ' ' + this.generate(node.path)
2466
- }
2467
- generateLoadStatement(node) {
2468
- return this.indent() + 'load ' + this.generate(node.path) + (node.wrap ? ', true' : '')
2469
- }
2470
-
2471
- generateIncludeStatement(node) {
2472
- return this.indent() + 'include ' + node.modules.map(m => this.generate(m)).join(', ')
2473
- }
2474
- generateExtendStatement(node) {
2475
- return this.indent() + 'extend ' + node.modules.map(m => this.generate(m)).join(', ')
2476
- }
2477
- generatePrependStatement(node) {
2478
- return this.indent() + 'prepend ' + node.modules.map(m => this.generate(m)).join(', ')
2479
- }
2480
-
2481
- generateAttrAccessor(node) {
2482
- return this.indent() + node.accessorType + ' ' + node.attributes.map(a => this.generate(a)).join(', ')
2483
- }
2484
-
2485
- generateAliasStatement(node) {
2486
- return this.indent() + 'alias ' + this.generate(node.newName) + ' ' + this.generate(node.oldName)
2487
- }
2488
- generateUndefStatement(node) {
2489
- return this.indent() + 'undef ' + node.methods.map(m => this.generate(m)).join(', ')
2490
- }
2491
-
2492
- generateExpressionStatement(node) {
2493
- return this.indent() + this.generate(node.expression)
2494
- }
2495
-
2496
- generateMethodCall(node) {
2497
- let result = ''
2498
- if (node.receiver) {
2499
- result = this.generate(node.receiver)
2500
- result += node.safeNavigation ? '&.' : '.'
2501
- }
2502
- result += node.method
2503
- if (node.arguments.length || node.block) {
2504
- if (node.arguments.length) {
2505
- result += '(' + node.arguments.map(a => this.generate(a)).join(', ') + ')'
2506
- }
2507
- if (node.block) {
2508
- result += ' ' + this.generate(node.block)
2509
- }
2510
- }
2511
- return result
2512
- }
2513
-
2514
- generateBlock(node) {
2515
- if (node.body.length === 1 && node.body[0].type === 'ExpressionStatement') {
2516
- let result = '{ '
2517
- if (node.parameters.length) {
2518
- result += '|' + node.parameters.map(p => this.generate(p)).join(', ') + '| '
2519
- }
2520
- result += this.generate(node.body[0].expression) + ' }'
2521
- return result
2522
- }
2523
-
2524
- let result = 'do'
2525
- if (node.parameters.length) {
2526
- result += ' |' + node.parameters.map(p => this.generate(p)).join(', ') + '|'
2527
- }
2528
- result += '\n'
2529
- this.indentLevel++
2530
- result += node.body.map(s => this.generate(s)).join('\n')
2531
- this.indentLevel--
2532
- result += '\n' + this.indent() + 'end'
2533
- return result
2534
- }
2535
-
2536
- generateBlockParameter(node) {
2537
- let result = ''
2538
- if (node.splat) result += '*'
2539
- if (node.doubleSplat) result += '**'
2540
- if (node.blockArg) result += '&'
2541
- result += node.name
2542
- if (node.defaultValue) result += ' = ' + this.generate(node.defaultValue)
2543
- return result
2544
- }
2545
-
2546
- generateLambdaLiteral(node) {
2547
- let result = '-> '
2548
- if (node.parameters.length) {
2549
- result += '(' + node.parameters.map(p => this.generate(p)).join(', ') + ') '
2550
- }
2551
- result += '{ '
2552
- result += node.body.map(s => this.generate(s).trim()).join('; ')
2553
- result += ' }'
2554
- return result
2555
- }
2556
-
2557
- generateProcLiteral(node) {
2558
- let result = 'proc { '
2559
- if (node.parameters.length) {
2560
- result += '|' + node.parameters.map(p => this.generate(p)).join(', ') + '| '
2561
- }
2562
- result += node.body.map(s => this.generate(s).trim()).join('; ')
2563
- result += ' }'
2564
- return result
2565
- }
2566
-
2567
- generateBinaryExpression(node) {
2568
- return `${this.generate(node.left)} ${node.operator} ${this.generate(node.right)}`
2569
- }
2570
- generateUnaryExpression(node) {
2571
- return node.prefix ? `${node.operator}${this.generate(node.argument)}` : `${this.generate(node.argument)}${node.operator}`
2572
- }
2573
- generateAssignmentExpression(node) {
2574
- return `${this.generate(node.left)} ${node.operator} ${this.generate(node.right)}`
2575
- }
2576
- generateLogicalExpression(node) {
2577
- const op = node.operator === '&&' ? '&&' : '||'
2578
- return `${this.generate(node.left)} ${op} ${this.generate(node.right)}`
2579
- }
2580
- generateConditionalExpression(node) {
2581
- return `${this.generate(node.test)} ? ${this.generate(node.consequent)} : ${this.generate(node.alternate)}`
2582
- }
2583
-
2584
- generateIndexAccess(node) {
2585
- return `${this.generate(node.object)}[${this.generate(node.index)}]`
2586
- }
2587
- generateIndexAssignment(node) {
2588
- return `${this.generate(node.object)}[${this.generate(node.index)}] = ${this.generate(node.value)}`
2589
- }
2590
-
2591
- generateSuperCall(node) {
2592
- let result = 'super'
2593
- if (node.arguments) {
2594
- result += '(' + node.arguments.map(a => this.generate(a)).join(', ') + ')'
2595
- }
2596
- return result
2597
- }
2598
-
2599
- generateYieldExpression(node) {
2600
- let result = 'yield'
2601
- if (node.arguments.length) {
2602
- result += ' ' + node.arguments.map(a => this.generate(a)).join(', ')
2603
- }
2604
- return result
2605
- }
2606
-
2607
- generateSelfExpression() { return 'self' }
2608
- generateDefinedExpression(node) { return `defined?(${this.generate(node.expression)})` }
2609
- generateNotExpression(node) { return `not ${this.generate(node.argument)}` }
2610
-
2611
- generateSplatExpression(node) { return '*' + this.generate(node.argument) }
2612
- generateDoubleSplatExpression(node) { return '**' + this.generate(node.argument) }
2613
-
2614
- generateRangeLiteral(node) {
2615
- return `${this.generate(node.start)}${node.exclusive ? '...' : '..'}${this.generate(node.end)}`
2616
- }
2617
-
2618
- generateArrayLiteral(node) {
2619
- return '[' + node.elements.map(e => this.generate(e)).join(', ') + ']'
2620
- }
2621
-
2622
- generateHashLiteral(node) {
2623
- return '{ ' + node.pairs.map(p => this.generate(p)).join(', ') + ' }'
2624
- }
2625
-
2626
- generateHashPair(node) {
2627
- if (node.key.type === 'Symbol') {
2628
- return `${node.key.value}: ${this.generate(node.value)}`
2629
- }
2630
- return `${this.generate(node.key)} => ${this.generate(node.value)}`
2631
- }
2632
-
2633
- generateIdentifier(node) { return node.name }
2634
- generateInstanceVariable(node) { return '@' + node.name }
2635
- generateClassVariable(node) { return '@@' + node.name }
2636
- generateGlobalVariable(node) { return '$' + node.name }
2637
- generateSymbol(node) { return ':' + node.value }
2638
-
2639
- generateStringLiteral(node) { return node.raw || `"${node.value}"` }
2640
- generateInterpolatedString(node) {
2641
- let result = '"'
2642
- for (const part of node.parts) {
2643
- if (part.type === 'StringPart') {
2644
- result += part.value
2645
- } else {
2646
- result += '#{' + this.generate(part.expression) + '}'
2647
- }
2648
- }
2649
- result += '"'
2650
- return result
2651
- }
2652
-
2653
- generateNumericLiteral(node) { return node.raw || String(node.value) }
2654
- generateFloatLiteral(node) { return node.raw || String(node.value) }
2655
- generateRationalLiteral(node) { return node.raw }
2656
- generateComplexLiteral(node) { return node.raw }
2657
- generateBooleanLiteral(node) { return node.value ? 'true' : 'false' }
2658
- generateNilLiteral() { return 'nil' }
2659
-
2660
- generateRegexpLiteral(node) { return `/${node.pattern}/${node.flags}` }
2661
- generatePercentArray(node) { return `%${node.type}(${node.elements.join(' ')})` }
2662
- generateBacktickLiteral(node) { return '`' + node.command + '`' }
2663
-
2664
- generateMatchExpression(node) { return `${this.generate(node.left)} =~ ${this.generate(node.right)}` }
2665
- generateNotMatchExpression(node) { return `${this.generate(node.left)} !~ ${this.generate(node.right)}` }
2666
-
2667
- generateParenthesizedExpression(node) { return '(' + this.generate(node.expression) + ')' }
2668
-
2669
- generateFile() { return '__FILE__' }
2670
- generateLine() { return '__LINE__' }
2671
- generateEncoding() { return '__ENCODING__' }
2672
- }
2673
-
2674
- module.exports = {
2675
- RubyLexer,
2676
- RubyParser,
2677
- RubyCodeGenerator,
2678
- RUBY_KEYWORDS,
2679
- RUBY_METHODS
2680
- }