├── .gitignore ├── LICENSE ├── README.md ├── builder.py ├── control.lua ├── controller ├── FlangChip.lua └── GlobalData.lua ├── data.lua ├── graphics ├── close.png ├── empty.png ├── flangchip.png ├── play.png └── stop.png ├── info.json ├── lang ├── _interpreterCompleteDriver.lua ├── _interpreterCompleteDriverPerf.lua ├── _interpreterDriver.lua ├── _interpreterRepeatDriver.lua ├── _lexerDriver.lua ├── _parserDriver.lua ├── _scannerDriver.lua ├── base │ ├── character.lua │ ├── flang_import.lua │ ├── interpreter.lua │ ├── lexer.lua │ ├── lua_functions │ │ ├── core.lua │ │ ├── string.lua │ │ └── table.lua │ ├── node.lua │ ├── parser.lua │ ├── scanner.lua │ ├── scope.lua │ ├── symbols.lua │ ├── token.lua │ └── util.lua └── samples │ ├── 1.flang │ ├── 2.flang │ ├── 3.flang │ ├── 4.flang │ ├── array1.flang │ ├── array2.flang │ ├── array3.flang │ ├── array4.flang │ ├── b1.flang │ ├── broken_string.flang │ ├── comment1.flang │ ├── complete.flang │ ├── conditional_chaining1.flang │ ├── efor1.flang │ ├── efor2.flang │ ├── eq1.flang │ ├── eq2.flang │ ├── flow_control1.flang │ ├── flow_control2.flang │ ├── flow_control3.flang │ ├── for1.flang │ ├── for_array1.flang │ ├── func1.flang │ ├── func2.flang │ ├── func3.flang │ ├── funcCall1.flang │ ├── funcCall2.flang │ ├── hard_math.flang │ ├── if1.flang │ ├── if2.flang │ ├── iter_for1.flang │ ├── iter_for2.flang │ ├── repeat_execution_test.flang │ ├── s1.flang │ ├── s2.flang │ ├── scope1.flang │ ├── scope2.flang │ ├── scope3.flang │ ├── scope4.flang │ ├── string_methods1.flang │ ├── string_methods2.flang │ └── wacky.flang ├── locale └── en │ └── config.cfg ├── prototypes ├── custominput.lua ├── entity.lua ├── item.lua ├── recipe.lua ├── sprite.lua └── style.lua └── save-commands.json /.gitignore: -------------------------------------------------------------------------------- 1 | releases/ 2 | 3 | # auto-gen below 4 | # Compiled Lua sources 5 | luac.out 6 | 7 | # luarocks build files 8 | *.src.rock 9 | *.zip 10 | *.tar.gz 11 | 12 | # Object files 13 | *.o 14 | *.os 15 | *.ko 16 | *.obj 17 | *.elf 18 | 19 | # Precompiled Headers 20 | *.gch 21 | *.pch 22 | 23 | # Libraries 24 | *.lib 25 | *.a 26 | *.la 27 | *.lo 28 | *.def 29 | *.exp 30 | 31 | # Shared objects (inc. Windows DLLs) 32 | *.dll 33 | *.so 34 | *.so.* 35 | *.dylib 36 | 37 | # Executables 38 | *.exe 39 | *.out 40 | *.app 41 | *.i*86 42 | *.x86_64 43 | *.hex 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Julian Richard Contreras 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flang 2 | Factorio Interpreter in Lua 3 | 4 | "So what can do you with Flang?” 5 | https://gfycat.com/ScientificPinkGoosefish 6 | 7 | # description 8 | 9 | The EBNF language definition can be found in `base\parser.lua`. 10 | 11 | The flow from source file to symbol table is as follows: 12 | 13 | [source file] -> [characters] -> `base\scanner.lua` -> `base\lexer.lua` -> [tokens] -> `base\parser.lua` -> [nodes] -> `base\interpreter.lua` -> [symbol table] 14 | 15 | ## symbol table 16 | 17 | The symbol table is a mapping between variable names and values. 18 | 19 | # drivers 20 | The drivers run different functionality modules of the language. For example, the `_lexerDriver.lua` will run the lexer and print tokens of the source input file. 21 | 22 | # example 23 | 24 | A complete example of Flang can be found in the `samples/complete.flang` file. Running `lua _interpreterCompleteDriver.lua` will run this example and assertions. An example successful output (with lexer tokens) is printed below: 25 | 26 | # building to mod 27 | The builder.py script creates a zipped mod file. 28 | 29 | If using Atom, this script will be run on-save automatically, see https://atom.io/packages/save-commands 30 | -------------------------------------------------------------------------------- /builder.py: -------------------------------------------------------------------------------- 1 | # creates a release 2 | import json 3 | import os,sys 4 | import shutil 5 | 6 | curDir = os.path.dirname(os.path.abspath(sys.argv[0])) 7 | 8 | # join some folder/file from the root to it's full path 9 | def local_file_path(filename): 10 | return os.path.join(curDir, filename) 11 | 12 | def release_file_path(filename): 13 | return os.path.join(release_folder_path, filename) 14 | 15 | # read the info.json for the release name 16 | def get_version(): 17 | filename = "info.json" 18 | fileAbsolutePath = local_file_path(filename) 19 | with open(fileAbsolutePath) as data_file: 20 | config = json.load(data_file) 21 | return config["version"] 22 | 23 | # read the info.json for the mod name 24 | def get_mod_name(): 25 | filename = "info.json" 26 | fileAbsolutePath = local_file_path(filename) 27 | with open(fileAbsolutePath) as data_file: 28 | config = json.load(data_file) 29 | return config["name"] 30 | 31 | # make the release 32 | version = get_version() 33 | release_name = get_mod_name() + "_" + version 34 | # release_folder_path = local_file_path(os.path.join("releases", release_name)) 35 | release_folder_path = os.path.join("C:\\Users\\jrcontre\\AppData\\Roaming\\Factorio\\mods", release_name) 36 | print("release_folder_path", release_folder_path) 37 | 38 | if (os.path.exists(release_folder_path)): 39 | print("Release already exists at " + release_folder_path + " . Deleting path.") 40 | shutil.rmtree(release_folder_path, ignore_errors=True) 41 | # exit(0) 42 | else: 43 | print("Creating release dir at " + release_folder_path) 44 | os.mkdir(release_folder_path) 45 | 46 | # copy the files to releases 47 | whitelisted_files = ["lang", "controller", "locale", "prototypes", "graphics", "LICENSE", "control.lua", "data.lua", "info.json"] 48 | 49 | for file in whitelisted_files: 50 | try: 51 | localpath = local_file_path(file) 52 | releasepath = release_file_path(file) 53 | 54 | print("localpath", localpath) 55 | print("releasepath", releasepath) 56 | if (os.path.isfile(localpath)): 57 | print("Copying file from localpath " + localpath + " to releasepath " + releasepath) 58 | shutil.copyfile(localpath, releasepath) 59 | else: 60 | print("Copying tree from localpath " + localpath + " to releasepath " + releasepath) 61 | shutil.copytree(localpath, releasepath) 62 | except Exception as e: 63 | pass 64 | 65 | # create the zip file 66 | # shutil.make_archive(release_folder_path, 'zip', release_folder_path) 67 | -------------------------------------------------------------------------------- /control.lua: -------------------------------------------------------------------------------- 1 | require("controller.FlangChip") 2 | GlobalData = require("controller.GlobalData") 3 | Flang.tick = 0 4 | 5 | -- Mappings 6 | local PLAYER_ID_TO_ENTITY_MAPPING = {} 7 | -- Mapping from entity ID -> FlangChip 8 | local CHIP_TABLE = {} 9 | 10 | -- Some constants 11 | local FLANG_CHIP_ENTITY_NAME = "flang-chip" 12 | local INVIS_FLANG_CHIP_ENTITY_NAME = "invis-flang-chip" 13 | 14 | function get_player_last_chip_entity(player_id) 15 | -- create the table if needed 16 | if PLAYER_ID_TO_ENTITY_MAPPING["player_last_chip_entity_mapping"] then 17 | local entity = PLAYER_ID_TO_ENTITY_MAPPING["player_last_chip_entity_mapping"][player_id] 18 | if entity and entity.valid then 19 | return entity 20 | else 21 | -- get rid of this hot entity 22 | PLAYER_ID_TO_ENTITY_MAPPING["player_last_chip_entity_mapping"][player_id] = nil 23 | return nil 24 | end 25 | else 26 | return nil 27 | end 28 | end 29 | 30 | function set_player_last_chip_entity(player_id, entity) 31 | if not PLAYER_ID_TO_ENTITY_MAPPING["player_last_chip_entity_mapping"] then 32 | PLAYER_ID_TO_ENTITY_MAPPING["player_last_chip_entity_mapping"] = {} 33 | end 34 | 35 | PLAYER_ID_TO_ENTITY_MAPPING["player_last_chip_entity_mapping"][player_id] = entity 36 | end 37 | 38 | --[[ 39 | The source is the preloaded source from storage 40 | ]] 41 | function create_editor_window(player, source) 42 | -- Get rid of any window that's already present 43 | if player.gui.left.flang_parent_window_flow then player.gui.left.flang_parent_window_flow.destroy() end 44 | 45 | -- create the parent window for the whole thing 46 | local flang_parent_window_flow = player.gui.left.flang_parent_window_flow 47 | if not flang_parent_window_flow then 48 | -- create the parent flow 49 | flang_parent_window_flow = player.gui.left.add{type = "flow", name = "flang_parent_window_flow", direction = "vertical"} 50 | end 51 | 52 | -- create the menu 53 | menu_flow = flang_parent_window_flow.add({type = "flow", name = "flang_menu_flow", direction = "horizontal"}) 54 | -- inside the menu we add the buttons and stuff 55 | button_style = "slot_button_style" 56 | close_button = menu_flow.add({ 57 | type = "sprite-button", 58 | name = "flang_menu_close_button", 59 | sprite = "close"}) 60 | play_button = menu_flow.add({ 61 | type = "sprite-button", 62 | name = "flang_menu_play_button", 63 | sprite = "play"}) 64 | stop_button = menu_flow.add({ 65 | type = "sprite-button", 66 | name = "flang_menu_stop_button", 67 | sprite = "stop"}) 68 | 69 | -- create the editor 70 | editor_window = flang_parent_window_flow.add{type="text-box", name="flang_editor_window", 71 | style="flang_editor_window_style", text=source} 72 | 73 | -- create the info window 74 | info_window = flang_parent_window_flow.add{ 75 | type="text-box", name="flang_info_window", 76 | style="flang_info_window_style", 77 | text="nothing here... yet" 78 | } 79 | end 80 | 81 | function close_editor_window(player, flangchip_entity) 82 | if player.gui.left.flang_parent_window_flow then 83 | player.gui.left.flang_parent_window_flow.destroy() 84 | end 85 | end 86 | 87 | --[[ 88 | Called when the chip dies or is mined. 89 | ]] 90 | function delete_chip_controller(entity) 91 | if is_entity_flang_chip(entity) then 92 | local id = entity.unit_number 93 | 94 | -- delete from global storage 95 | GlobalData.delete_entity_data(id) 96 | 97 | -- delete from local storage 98 | local chip = CHIP_TABLE[id] 99 | if (chip and chip.invis_chip) then 100 | chip.invis_chip.destroy() 101 | end 102 | CHIP_TABLE[id] = nil 103 | end 104 | end 105 | 106 | function create_chip_controller(entity, built_from_robot) 107 | if is_entity_flang_chip(entity) then 108 | local id = entity.unit_number 109 | local sourceCode = "" 110 | 111 | -- Detect if a chip already exists at the same position 112 | local invis_chip 113 | local existingInvisChip = get_existing_entity_name_at_parent(entity, INVIS_FLANG_CHIP_ENTITY_NAME) 114 | 115 | if (existingInvisChip) then 116 | -- TODO I don't think this case will ever be hit in real-world use 117 | -- The invis chip is only made in the other block AFTER this chip already exists 118 | invis_chip = existingInvisChip 119 | -- We already have an encoded chip here, read the contents 120 | sourceCode = decode_data_from_invis_chip(existingInvisChip) 121 | else 122 | -- Create an invis chip since none already exist 123 | invis_chip = create_invis_chip(entity) 124 | 125 | if (built_from_robot) then 126 | -- This will be deconstructed and fulfill by the robot shortly 127 | invis_chip.order_deconstruction(entity.force) 128 | end 129 | end 130 | 131 | -- create the first record of the entity 132 | local object_data = GlobalData.new_data_object() 133 | object_data.entity = entity 134 | object_data.invis_chip = invis_chip 135 | GlobalData.write_entity_data(id, object_data) 136 | 137 | -- create the local chip 138 | local chip = FlangChip:new({ 139 | entity = entity, 140 | printer = editor_window_print, 141 | invis_chip = invis_chip, 142 | source = sourceCode 143 | }) 144 | CHIP_TABLE[id] = chip 145 | 146 | elseif is_entity_invis_flang_chip(entity) then 147 | local existingData = decode_data_from_invis_chip(entity) 148 | 149 | -- Find the existing flang chip here 150 | local existingFlangEntity = get_existing_entity_name_at_parent(entity, FLANG_CHIP_ENTITY_NAME) 151 | if (not is_entity_flang_chip(existingFlangEntity)) then 152 | return 153 | end 154 | 155 | -- Update our tables to reflect the chip relationship 156 | 157 | -- Global table update 158 | local chipEntityId = existingFlangEntity.unit_number 159 | GlobalData.write_invis_chip(chipEntityId, entity) 160 | 161 | -- Local chip update 162 | local chip = CHIP_TABLE[chipEntityId] 163 | chip.invis_chip = entity 164 | 165 | -- Update the chip source code with the decoded value here 166 | update_entity_source_code(chipEntityId, existingData) 167 | end 168 | end 169 | 170 | -------------------------- Chip Handling --------------------- 171 | 172 | -------------------------------------------------------------- 173 | 174 | script.on_event(defines.events.on_tick, function(event) 175 | if (event.tick % (30*1) == 0) then 176 | Flang.tick = Flang.tick + 1 177 | for entity_id, chip in pairs(CHIP_TABLE) do 178 | chip:execute() 179 | end 180 | end 181 | end) 182 | 183 | script.on_event(defines.events.on_built_entity, function(event) 184 | create_chip_controller(event.created_entity, false) 185 | end) 186 | 187 | script.on_event(defines.events.on_robot_built_entity, function(event) 188 | create_chip_controller(event.created_entity, true) 189 | end) 190 | 191 | script.on_event(defines.events.on_entity_settings_pasted, function(event) 192 | --[[ 193 | on_entity_settings_pasted 194 | Called after entity copy-paste is done. 195 | 196 | Contains 197 | player_index :: uint 198 | source :: LuaEntity: The source entity settings have been copied from. 199 | destination :: LuaEntity: The destination entity settings have been copied to. 200 | ]] 201 | -- Copy over the source to the new chip 202 | 203 | -- Get the entities 204 | local sourceEntity = event.source 205 | local destinationEntity = event.destination 206 | if (is_entity_flang_chip(sourceEntity) and is_entity_flang_chip(destinationEntity)) then 207 | local sourceId = sourceEntity.unit_number 208 | local destinationId = destinationEntity.unit_number 209 | 210 | -- Write to the global data 211 | local sourceChipData = GlobalData.get_entity_data(sourceId) 212 | local sourceCode = sourceChipData.source 213 | update_entity_source_code(destinationId, sourceCode) 214 | end 215 | end) 216 | 217 | -------------------------- GUI ------------------------------- 218 | 219 | -------------------------------------------------------------- 220 | 221 | script.on_event("flang-open-editor", function(event) 222 | local player = game.players[event.player_index] 223 | 224 | -- Make sure the entity is a flang chip 225 | if player.selected and is_entity_flang_chip(player.selected) then 226 | local entity = player.selected 227 | 228 | local source = GlobalData.get_entity_data(entity.unit_number)["source"] 229 | set_player_last_chip_entity(event.player_index, entity) 230 | create_editor_window(player, source) 231 | end 232 | end) 233 | 234 | script.on_event(defines.events.on_gui_click, function(event) 235 | local player = game.players[event.player_index] 236 | 237 | -- if the close button was clicked, close the parent window 238 | if event.element.name == "flang_menu_close_button" then 239 | close_editor_window(player) 240 | elseif event.element.name == "flang_menu_play_button" then 241 | local entity = get_player_last_chip_entity(event.player_index) 242 | if entity then 243 | local id = entity.unit_number 244 | 245 | GlobalData.write_entity_is_running(id, true) 246 | 247 | -- The chip should exist already 248 | local chip = CHIP_TABLE[id] 249 | chip:start_execution() 250 | end 251 | elseif event.element.name == "flang_menu_stop_button" then 252 | local entity = get_player_last_chip_entity(event.player_index) 253 | if entity then 254 | local id = entity.unit_number 255 | 256 | GlobalData.write_entity_is_running(id, false) 257 | 258 | -- The chip should exist already 259 | local chip = CHIP_TABLE[id] 260 | chip:stop_execution() 261 | end 262 | end 263 | end) 264 | 265 | script.on_event(defines.events.on_gui_text_changed, function(event) 266 | local player = game.players[event.player_index] 267 | 268 | if event.element.name == "flang_editor_window" then 269 | local text = event.element.text 270 | 271 | local entity = get_player_last_chip_entity(event.player_index) 272 | if entity then 273 | local id = entity.unit_number 274 | -- We add a newline since the gui editor apparently doesn't have EOF 275 | update_entity_source_code(id, text .. "\n") 276 | end 277 | end 278 | end) 279 | 280 | -------------------------- Deletion -------------------------- 281 | 282 | -------------------------------------------------------------- 283 | 284 | script.on_event(defines.events.on_entity_died, function(event) 285 | delete_chip_controller(event.entity) 286 | end) 287 | 288 | script.on_event(defines.events.on_player_mined_entity, function(event) 289 | delete_chip_controller(event.entity) 290 | end) 291 | 292 | script.on_event(defines.events.on_robot_mined_entity, function(event) 293 | delete_chip_controller(event.entity) 294 | end) 295 | 296 | -------------------------- Initialization -------------------- 297 | 298 | -------------------------------------------------------------- 299 | 300 | script.on_init(function() 301 | -- recreate the controller table from the global table 302 | -- player_log_print("on init") 303 | end) 304 | 305 | script.on_configuration_changed(function() 306 | -- recreate the controller table from the global table 307 | -- player_log_print("on config changed") 308 | end) 309 | 310 | script.on_load(function() 311 | -- recreate the controller table from the global table 312 | for entity_id, chip_data in pairs(GlobalData.get_all_entities()) do 313 | local chip = FlangChip:new({ 314 | entity = chip_data["entity"], 315 | source = chip_data["source"], 316 | is_running = chip_data["is_running"], 317 | invis_chip = chip_data["invis_chip"], 318 | printer = editor_window_print 319 | }) 320 | CHIP_TABLE[entity_id] = chip 321 | end 322 | end) 323 | 324 | -------------------------- Chip Data Updates ----------------- 325 | 326 | -------------------------------------------------------------- 327 | 328 | function update_entity_source_code(destinationEntityId, sourceCode) 329 | -- Update the global data 330 | GlobalData.write_entity_source(destinationEntityId, sourceCode) 331 | 332 | -- Write to the local data 333 | local chip = CHIP_TABLE[destinationEntityId] 334 | chip:update_source(sourceCode) 335 | 336 | -- Write to the invis_chip 337 | local invis_chip = chip.invis_chip 338 | if (is_entity_invis_flang_chip(invis_chip)) then 339 | encode_data_onto_invis_chip(invis_chip, sourceCode) 340 | end 341 | end 342 | 343 | ----------------- Invis Chip --------------------------------- 344 | 345 | -------------------------------------------------------------- 346 | 347 | function create_invis_chip(entity) 348 | local surface = entity.surface 349 | local invis_chip = surface.create_entity({ 350 | name = INVIS_FLANG_CHIP_ENTITY_NAME, 351 | position = entity.position, 352 | direction = entity.direction, 353 | force = entity.force 354 | }) 355 | invis_chip.destructible = false 356 | return invis_chip 357 | end 358 | 359 | function encode_data_onto_invis_chip(entity, stringData) 360 | -- See https://lua-api.factorio.com/latest/LuaEntity.html#LuaEntity.alert_parameters 361 | -- https://lua-api.factorio.com/latest/Concepts.html#ProgrammableSpeakerAlertParameters 362 | 363 | local speakerAlertParameters = { 364 | alert_message = stringData, 365 | 366 | show_alert = false, 367 | show_on_map = false, 368 | -- signal ID is not required 369 | } 370 | 371 | if (is_entity_invis_flang_chip(entity)) then 372 | entity.alert_parameters = speakerAlertParameters 373 | end 374 | end 375 | 376 | function decode_data_from_invis_chip(entity) 377 | if (is_entity_invis_flang_chip(entity)) then 378 | return entity.alert_parameters.alert_message 379 | end 380 | end 381 | 382 | function get_existing_entity_name_at_parent(parentEntity, targetName) 383 | -- Any invis chips will exist at the same position 384 | local entitiesAtSamePosition = parentEntity.surface.find_entities_filtered({ 385 | position = parentEntity.position, 386 | name = targetName 387 | }) 388 | 389 | for _,matchingEntity in pairs(entitiesAtSamePosition) do 390 | if (matchingEntity ~= parentEntity) then 391 | return matchingEntity 392 | end 393 | end 394 | 395 | player_log_print("Couldn't find existing entity with target name " .. targetName) 396 | return nil 397 | end 398 | 399 | -------------------------- Misc ------------------------------ 400 | 401 | -------------------------------------------------------------- 402 | 403 | function is_entity_flang_chip(entity) 404 | return entity and entity.name == FLANG_CHIP_ENTITY_NAME and entity.valid 405 | end 406 | 407 | function is_entity_invis_flang_chip(entity) 408 | return entity and entity.name == INVIS_FLANG_CHIP_ENTITY_NAME and entity.valid 409 | end 410 | 411 | function player_log_print(msg) 412 | if game == nil then 413 | return 414 | end 415 | 416 | for index,player in pairs(game.connected_players) do 417 | player.print(msg) 418 | end 419 | end 420 | 421 | function editor_window_print(spawningEntity, msg, should_clear) 422 | if game == nil then 423 | return 424 | end 425 | 426 | for playerIndex,player in pairs(game.connected_players) do 427 | local lastPlayerChipEntity = get_player_last_chip_entity(player.index) 428 | if (lastPlayerChipEntity == nil) then 429 | return 430 | end 431 | 432 | if (spawningEntity.unit_number ~= lastPlayerChipEntity.unit_number) then 433 | return 434 | end 435 | 436 | if player.gui.left.flang_parent_window_flow and player.gui.left.flang_parent_window_flow.flang_info_window then 437 | info_window = player.gui.left.flang_parent_window_flow.flang_info_window 438 | if (should_clear) then 439 | info_window.text = "" 440 | end 441 | 442 | if (info_window.text == "") then 443 | info_window.text = msg 444 | else 445 | info_window.text = info_window.text .. "\n" .. msg 446 | end 447 | end 448 | end 449 | end 450 | 451 | function print_pairs(table) 452 | for k,v in pairs(table) do 453 | player_log_print("key" .. k .. " val " .. v) 454 | end 455 | end 456 | -------------------------------------------------------------------------------- /controller/FlangChip.lua: -------------------------------------------------------------------------------- 1 | require("lang.base.flang_import") 2 | 3 | FlangChip = {} 4 | FlangChip.__index = FlangChip 5 | 6 | function FlangChip:new(o) 7 | if not o then 8 | error("nil constructor!") 9 | end 10 | 11 | if (not o.entity) then 12 | error("nil entity!") 13 | end 14 | 15 | o = { 16 | entity = o.entity, 17 | source = o.source or "", 18 | interpreter = nil, 19 | 20 | is_running = o.is_running or false, 21 | 22 | invis_chip = o.invis_chip, 23 | 24 | -- straight to editor window logging section function 25 | printer = o.printer 26 | } 27 | 28 | setmetatable(o, self) 29 | self.__index = self 30 | 31 | return o 32 | end 33 | 34 | function FlangChip:update_source(source) 35 | self:stop_execution() 36 | self.source = source 37 | end 38 | 39 | --[[ 40 | Start the program, recreate the Flang interpreter. 41 | Does not call interpret however, simply readies the interpreter for it 42 | ]] 43 | function FlangChip:start_execution() 44 | -- recreate everything 45 | local success, result = pcall(create_flang_interpreter, self.source, self.entity, self.printer) 46 | if success then 47 | self.interpreter = result 48 | self.is_running = true 49 | self:print("interpreter execution successful", true) 50 | else 51 | self:print("interpreter error", true) 52 | self:on_error(result) 53 | self.is_running = false 54 | end 55 | 56 | return result 57 | end 58 | 59 | --[[ 60 | Stops the program. 61 | Does not modify the interpreter 62 | ]] 63 | function FlangChip:stop_execution() 64 | if (self.is_running) then 65 | self:print("execution stopped") 66 | end 67 | self.is_running = false 68 | end 69 | 70 | function FlangChip:execute() 71 | if not self.is_running then return end 72 | 73 | if not self.interpreter then 74 | -- create the interpreter 75 | self:start_execution() 76 | 77 | -- if the interpreter failed here, then just return 78 | -- this check is duplicated so we don't keep trying to create the interpreter 79 | -- and error loop into oblivion 80 | if not self.is_running then return end 81 | end 82 | 83 | self:print("", true) 84 | local success, result = pcall(self.interpreter.interpret, self.interpreter) 85 | if not success then 86 | self.is_running = false 87 | self:printGlobalVars() 88 | self:print("\nexecution error") 89 | self:on_error(result) 90 | end 91 | end 92 | 93 | function FlangChip:on_error(error) 94 | -- fuck 95 | self:print("got error!") 96 | self:print(tostring(error)) 97 | end 98 | 99 | function FlangChip:printGlobalVars() 100 | self:print("Current vars: \n") 101 | -- result is our symbol table 102 | for k,value in pairs(self.interpreter.global_symbol_scope.variable_table) do 103 | if (Util.isTable(value)) then 104 | self:print(k .. " = " .. Util.set_to_string(value, true)) 105 | else 106 | self:print(k .. " = " .. tostring(value)) 107 | end 108 | end 109 | end 110 | 111 | function FlangChip:print(msg, shouldClear) 112 | if (shouldClear == nil) then 113 | shouldClear = false 114 | end 115 | self.printer(self.entity, msg, shouldClear) 116 | end 117 | 118 | function create_flang_interpreter(source, entity, printer) 119 | local lexer = Flang.Lexer:new({sourceText = source}) 120 | local parser = Flang.Parser:new({lexer = lexer}) 121 | 122 | -- Create our wrapper to pass in for function calls 123 | local wrapper = { 124 | entity = entity, 125 | printer = printer 126 | } 127 | local interpreter = Flang.Interpreter:new({parser = parser, wrapper = wrapper}) 128 | return interpreter 129 | end 130 | 131 | function FlangChip:__tostring() 132 | print("source:\n" .. self.source) 133 | end 134 | -------------------------------------------------------------------------------- /controller/GlobalData.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | A whole bunch of shit to save in the global table across saves. 3 | 4 | Note that while values can be Factorio objects, KEYS must be primitives 5 | 6 | see http://lua-api.factorio.com/latest/Global.html 7 | ]] 8 | 9 | local GlobalData = {} 10 | 11 | local GLOBAL_TABLE_NAME = "chip_data" 12 | 13 | --[[ 14 | An empty data object for marshalling 15 | ]] 16 | function GlobalData.new_data_object() 17 | return { 18 | -- The code in the editor 19 | source = "", 20 | -- is the code running or nah? 21 | is_running = false, 22 | -- the entity referenced by the id 23 | entity = nil, 24 | invis_chip = nil 25 | } 26 | end 27 | 28 | function create_global_table() 29 | if not global[GLOBAL_TABLE_NAME] then 30 | -- create the table 31 | global[GLOBAL_TABLE_NAME] = {} 32 | end 33 | end 34 | 35 | function GlobalData.write_entity_data(entity_id, data_object) 36 | create_global_table() 37 | global[GLOBAL_TABLE_NAME][entity_id] = data_object 38 | end 39 | 40 | function GlobalData.write_entity_source(entity_id, source) 41 | local data = GlobalData.get_entity_data(entity_id) 42 | data["source"] = source 43 | GlobalData.write_entity_data(entity_id, data) 44 | end 45 | 46 | function GlobalData.write_entity_is_running(entity_id, is_running) 47 | local data = GlobalData.get_entity_data(entity_id) 48 | data["is_running"] = is_running 49 | GlobalData.write_entity_data(entity_id, data) 50 | end 51 | 52 | function GlobalData.write_invis_chip(entity_id, invis_chip) 53 | local data = GlobalData.get_entity_data(entity_id) 54 | data["invis_chip"] = invis_chip 55 | GlobalData.write_entity_data(entity_id, data) 56 | end 57 | 58 | function GlobalData.delete_entity_data(entity_id) 59 | GlobalData.write_entity_data(entity_id, nil) 60 | end 61 | 62 | --[[ 63 | Returns a data object for the saved entity in global state. 64 | 65 | If the entity hasn't been saved, then the data object will be empty 66 | ]] 67 | function GlobalData.get_entity_data(entity_id) 68 | create_global_table() 69 | 70 | if not global[GLOBAL_TABLE_NAME][entity_id] then 71 | global[GLOBAL_TABLE_NAME][entity_id] = GlobalData.new_data_object() 72 | end 73 | 74 | -- get the table 75 | return global[GLOBAL_TABLE_NAME][entity_id] 76 | end 77 | 78 | --[[ 79 | Reads whatever the fuck is present in the global table at the time of reading. 80 | Does not modify the global table in any way! 81 | ]] 82 | function GlobalData.get_all_entities() 83 | return global[GLOBAL_TABLE_NAME] or {} 84 | end 85 | 86 | return GlobalData 87 | -------------------------------------------------------------------------------- /data.lua: -------------------------------------------------------------------------------- 1 | require("prototypes.item") 2 | require("prototypes.entity") 3 | require("prototypes.recipe") 4 | require("prototypes.style") 5 | require("prototypes.sprite") 6 | require("prototypes.custominput") 7 | -------------------------------------------------------------------------------- /graphics/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radixdev/flang/664a2e009cd70f0dd21fae147d8ba6f328879c7f/graphics/close.png -------------------------------------------------------------------------------- /graphics/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radixdev/flang/664a2e009cd70f0dd21fae147d8ba6f328879c7f/graphics/empty.png -------------------------------------------------------------------------------- /graphics/flangchip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radixdev/flang/664a2e009cd70f0dd21fae147d8ba6f328879c7f/graphics/flangchip.png -------------------------------------------------------------------------------- /graphics/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radixdev/flang/664a2e009cd70f0dd21fae147d8ba6f328879c7f/graphics/play.png -------------------------------------------------------------------------------- /graphics/stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radixdev/flang/664a2e009cd70f0dd21fae147d8ba6f328879c7f/graphics/stop.png -------------------------------------------------------------------------------- /info.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Flang", 3 | "version": "0.1.0", 4 | "title": "Flang", 5 | "author": "radixdev", 6 | "contact": "", 7 | "homepage": "https://github.com/radixdev/flang", 8 | "factorio_version": "0.16", 9 | "dependencies": [ 10 | "base >= 0.15" 11 | ], 12 | "description": "This mod does flang. A programming language in Factorio." 13 | } 14 | -------------------------------------------------------------------------------- /lang/_interpreterCompleteDriver.lua: -------------------------------------------------------------------------------- 1 | require("base.flang_import") 2 | 3 | Flang.DEBUG_LOGGING = true 4 | 5 | filename = "samples/complete.flang" 6 | local f = assert(io.open(filename, "r")) 7 | local t = f:read("*all") 8 | f:close() 9 | 10 | print("===============") 11 | print(t) 12 | print("===============") 13 | 14 | local start_time = os.clock() 15 | lexer = Flang.Lexer:new({sourceText = t}) 16 | parser = Flang.Parser:new({lexer = lexer}) 17 | interpreter = Flang.Interpreter:new({parser = parser}) 18 | result = interpreter:interpret() 19 | local elapsed = os.clock() - start_time 20 | 21 | -- print out the symbol table 22 | print("global symbol table") 23 | symbol_table = interpreter.global_symbol_scope.variable_table 24 | for key,value in pairs(symbol_table) do 25 | if (type(value) == "table") then 26 | print(key .. " = " .. Util.set_to_string(value, true)) 27 | else 28 | print(key .. " = " .. tostring(value)) 29 | end 30 | end 31 | 32 | function assertEquals(var, expected) 33 | local actual = symbol_table[var] 34 | if actual ~= expected then 35 | print("Assertion error on "..dq(var)) 36 | print("Expected "..dq(expected).." but got "..dq(actual)) 37 | error("AssertionFailure") 38 | end 39 | end 40 | 41 | print("========ASSERTION CHECKS=======") 42 | assertEquals("pi", 3.141592) 43 | assertEquals("bTrue", true) 44 | assertEquals("aFalse", false) 45 | assertEquals("cTrue", true) 46 | assertEquals("bFive", 5) 47 | assertEquals("false2", true) 48 | assertEquals("dTrue", true) 49 | assertEquals("alpha15", 15) 50 | assertEquals("shouldBeFalse", false) 51 | assertEquals("eTrue", true) 52 | assertEquals("under_score_var", 12) 53 | assertEquals("boolTrue", true) 54 | 55 | assertEquals("modulus_2", 2) 56 | assertEquals("modulus_3", 3) 57 | 58 | assertEquals("ifShouldBe6", 6) 59 | assertEquals("ifShouldBe10", 10) 60 | assertEquals("ifShouldBe7", 7) 61 | 62 | assertEquals("forShouldBe100", 100) 63 | 64 | assertEquals("asShouldBe11", 11) 65 | assertEquals("asShouldBe8", 8) 66 | assertEquals("asShouldBe12", 12) 67 | assertEquals("asShouldBe2p5", 2.5) 68 | 69 | assertEquals("eForShouldBe10000", 10000) 70 | 71 | assertEquals("methodShouldBe5", 5) 72 | assertEquals("methodShouldBe18", 18) 73 | assertEquals("methodShouldBe100", 100) 74 | assertEquals("stringShouldBeHelloWorld", "Hello World!") 75 | 76 | assertEquals("arrayShouldBe20", 20) 77 | assertEquals("arrayShouldBe22", 22) 78 | assertEquals("arrayShouldBe24", 24) 79 | assertEquals("arrayShouldBeNeg17", -17) 80 | assertEquals("arrayShouldBe80", 80) 81 | assertEquals("arrayShouldBeOhh25", 0.25) 82 | assertEquals("arrayShouldBeMoon", "moon") 83 | 84 | assertEquals("arrayIteratorShouldBe15", 15) 85 | assertEquals("kvpArrayIteratorShouldBe9", 9) 86 | 87 | print("========ALL CHECKS PASSED=======") 88 | print(string.format("elapsed time: %.5fs or %.2fms\n", elapsed, elapsed * 1000)) 89 | -------------------------------------------------------------------------------- /lang/_interpreterCompleteDriverPerf.lua: -------------------------------------------------------------------------------- 1 | require("base.flang_import") 2 | 3 | Flang.DEBUG_LOGGING = false 4 | Flang.VERBOSE_LOGGING = false 5 | 6 | filename = "samples/complete.flang" 7 | local f = assert(io.open(filename, "r")) 8 | local t = f:read("*all") 9 | f:close() 10 | 11 | lexer = Flang.Lexer:new({sourceText = t}) 12 | parser = Flang.Parser:new({lexer = lexer}) 13 | interpreter = Flang.Interpreter:new({parser = parser}) 14 | local start_time = os.clock() 15 | 16 | for i=0, 100 do 17 | result = interpreter:interpret() 18 | end 19 | local elapsed = os.clock() - start_time 20 | 21 | time_per_run = elapsed / 100.0000 22 | print(string.format("elapsed time: %.5f", elapsed)) 23 | print(string.format("elapsed time per execution: %.5fs or %.2fms", time_per_run, time_per_run * 1000)) 24 | -------------------------------------------------------------------------------- /lang/_interpreterDriver.lua: -------------------------------------------------------------------------------- 1 | require("base.flang_import") 2 | 3 | filename = "samples/string_methods2.flang" 4 | local f = assert(io.open(filename, "r")) 5 | local t = f:read("*all") 6 | f:close() 7 | 8 | print("===== SOURCE =======") 9 | print(t) 10 | print("==== END SOURCE ====\n") 11 | 12 | Flang.DEBUG_LOGGING = not false 13 | Flang.VERBOSE_LOGGING = false 14 | 15 | local start_time = os.clock() 16 | lexer = Flang.Lexer:new({sourceText = t}) 17 | parser = Flang.Parser:new({lexer = lexer}) 18 | interpreter = Flang.Interpreter:new({parser = parser}) 19 | result = interpreter:interpret() 20 | local elapsed = os.clock() - start_time 21 | 22 | -- print out the symbol table 23 | print("===============") 24 | print("global symbol table") 25 | symbol_table = interpreter.global_symbol_scope.variable_table 26 | for key,value in pairs(symbol_table) do 27 | if (Util.isTable(value)) then 28 | print(key .. " = " .. Util.set_to_string(value, true)) 29 | else 30 | print(key .. " = " .. tostring(value)) 31 | end 32 | end 33 | 34 | print(string.format("elapsed time: %.5fs or %.2fms\n", elapsed, elapsed * 1000)) 35 | -------------------------------------------------------------------------------- /lang/_interpreterRepeatDriver.lua: -------------------------------------------------------------------------------- 1 | require("base.flang_import") 2 | 3 | filename = "samples/repeat_execution_test.flang" 4 | local f = assert(io.open(filename, "r")) 5 | local t = f:read("*all") 6 | f:close() 7 | 8 | print("===== SOURCE =======") 9 | print(t) 10 | print("==== END SOURCE ====\n") 11 | 12 | -- Flang.DEBUG_LOGGING = not false 13 | -- Flang.VERBOSE_LOGGING = false 14 | 15 | local start_time = os.clock() 16 | lexer = Flang.Lexer:new({sourceText = t}) 17 | parser = Flang.Parser:new({lexer = lexer}) 18 | interpreter = Flang.Interpreter:new({parser = parser}) 19 | 20 | for i = 1, 2, 1 do 21 | interpreter:interpret() 22 | print("") 23 | end 24 | 25 | local elapsed = os.clock() - start_time 26 | 27 | -- print out the symbol table 28 | print("===============") 29 | print("global symbol table") 30 | symbol_table = interpreter.global_symbol_scope.variable_table 31 | for key,value in pairs(symbol_table) do 32 | if (Util.isTable(value)) then 33 | print(key .. " = " .. Util.set_to_string(value, true)) 34 | else 35 | print(key .. " = " .. tostring(value)) 36 | end 37 | end 38 | 39 | print(string.format("elapsed time: %.5fs or %.2fms\n", elapsed, elapsed * 1000)) 40 | -------------------------------------------------------------------------------- /lang/_lexerDriver.lua: -------------------------------------------------------------------------------- 1 | require("base.flang_import") 2 | 3 | -- load our file 4 | filename = "samples/comment1.flang" 5 | local f = assert(io.open(filename, "r")) 6 | local t = f:read("*all") 7 | f:close() 8 | 9 | print("===============") 10 | print(t) 11 | print("===============") 12 | 13 | -- give it to the lexer 14 | lexer = Flang.Lexer:new({sourceText = t}) 15 | 16 | while true do 17 | token = lexer:get() 18 | print(tostring(token)) 19 | 20 | if (token.type == Symbols.EOF) then 21 | break 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lang/_parserDriver.lua: -------------------------------------------------------------------------------- 1 | require("base.flang_import") 2 | 3 | filename = "samples/eq2.flang" 4 | local f = assert(io.open(filename, "r")) 5 | local t = f:read("*all") 6 | f:close() 7 | 8 | print("===============") 9 | print(t) 10 | print("===============") 11 | 12 | -- give it to the lexer 13 | lexer = Flang.Lexer:new({sourceText = t}) 14 | 15 | -- then to the parser! 16 | parser = Flang.Parser:new({lexer = lexer}) 17 | 18 | tree = parser:parse() 19 | 20 | print("===============") 21 | tree:display() 22 | print("===============") 23 | -------------------------------------------------------------------------------- /lang/_scannerDriver.lua: -------------------------------------------------------------------------------- 1 | require("base.flang_import") 2 | 3 | -- load our file 4 | filename = "samples/1.flang" 5 | local f = assert(io.open(filename, "r")) 6 | local t = f:read("*all") 7 | f:close() 8 | 9 | print(t) 10 | 11 | -- give it to the scanner 12 | scanner = Flang.Scanner:new({sourceText = t}) 13 | 14 | while true do 15 | char = scanner:get() 16 | print(tostring(char)) 17 | 18 | if (char.cargo == Flang.Character.ENDMARK) then 19 | break 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lang/base/character.lua: -------------------------------------------------------------------------------- 1 | Character = {} 2 | Flang.Character = Character 3 | Character.__index = Character 4 | 5 | --[[ 6 | A Character object holds 7 | - one character (self.cargo) 8 | - the index of the character's position in the sourceText. 9 | - the index of the line where the character was found in the sourceText. 10 | - the index of the column in the line where the character was found in the sourceText. 11 | !- (a reference to) the entire sourceText (self.sourceText) 12 | 13 | This information will be available to a token that uses this character. 14 | If an error occurs, the token can use this information to report the 15 | line/column number where the error occurred, and to show an image of the 16 | line in sourceText where the error occurred. 17 | --]] 18 | 19 | Character.ENDMARK = "\0" 20 | 21 | --[[ 22 | (string) cargo - the character 23 | --]] 24 | function Character:new(o) 25 | if not o then 26 | error("nil constructor!") 27 | end 28 | 29 | o = { 30 | cargo = o.cargo, 31 | sourceIndex = o.sourceIndex, 32 | lineIndex = o.lineIndex, 33 | columnIndex = o.columnIndex 34 | } 35 | 36 | if (string.len(o.cargo) ~= 1) then 37 | error("character must be 1 char long. got: '" .. o.cargo .. "'") 38 | end 39 | 40 | setmetatable(o, self) 41 | self.__index = self 42 | return o 43 | end 44 | 45 | function Character:__tostring() 46 | cargo = self.cargo 47 | if cargo == " " then cargo = "\tSPACE" end 48 | if cargo == "\n" then cargo = "\tNEWLINE" end 49 | if cargo == "\t" then cargo = "\tTAB" end 50 | if cargo == Character.ENDMARK then cargo = "\tEOF" end 51 | 52 | return 53 | "line: '" .. self.lineIndex .. "'" 54 | .. "\t column: '" .. self.columnIndex .. "'" 55 | .. "\t sourceIndex: '" .. self.sourceIndex .. "'" 56 | .. "\t'" .. cargo .. "'" 57 | end 58 | -------------------------------------------------------------------------------- /lang/base/flang_import.lua: -------------------------------------------------------------------------------- 1 | -- imports all of the necessary FLANG classes in the base folder 2 | local folderOfThisFile = (...):match("(.-)[^%.]+$") 3 | 4 | function localRequire(module) 5 | require(folderOfThisFile..module) 6 | end 7 | 8 | -- Create our namespace 9 | if not Flang then Flang = {} end 10 | 11 | -- These imports MUST be in the proper dependency order or the loading will fail 12 | localRequire("util") 13 | 14 | localRequire("character") 15 | localRequire("symbols") 16 | localRequire("token") 17 | localRequire("scanner") 18 | localRequire("lexer") 19 | 20 | localRequire("node") 21 | localRequire("parser") 22 | localRequire("scope") 23 | localRequire("interpreter") 24 | 25 | -- Create the lua function (mods) namespace 26 | if not Flang.LuaFunction then Flang.LuaFunction = {} end 27 | 28 | -- Start loading lua functions 29 | localRequire("lua_functions/core") 30 | localRequire("lua_functions/table") 31 | localRequire("lua_functions/string") 32 | -------------------------------------------------------------------------------- /lang/base/interpreter.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Interpreter for the Flang language ;) 4 | 5 | "Uses" the visitor pattern to evaluate the AST from the parser. 6 | 7 | ]] 8 | 9 | Interpreter = {} 10 | Flang.Interpreter = Interpreter 11 | Interpreter.__index = Interpreter 12 | 13 | function Interpreter:new(o) 14 | if not o then 15 | error("nil constructor!") 16 | end 17 | 18 | o = { 19 | parser = o.parser, 20 | -- The wrapper contains info from the runner. 21 | wrapper = o.wrapper or {}, 22 | method_table_global = {}, 23 | 24 | current_symbol_scope = nil, 25 | 26 | global_symbol_scope = nil, 27 | tree = o.parser:parse() 28 | } 29 | 30 | setmetatable(o, self) 31 | self.__index = self 32 | return o 33 | end 34 | 35 | function Interpreter:error(msg) 36 | if (lastVisitedNode and lastVisitedNode.token and Util.isTable(lastVisitedNode.token)) then 37 | local errorMsg = msg .. "\nat " .. Util.set_to_string_dumb(lastVisitedNode.token) 38 | local source = self.parser.lexer.sourceText 39 | local errorLine = lastVisitedNode.token.lineIndex 40 | 41 | -- Print the line itself 42 | local lineNum = 0 43 | for line in source:gmatch("([^\n]*)\n?") do 44 | lineNum = lineNum + 1 45 | if (lineNum == errorLine) then 46 | errorMsg = errorMsg .. "\n> " .. line 47 | break 48 | end 49 | end 50 | 51 | error(errorMsg) 52 | else 53 | error(msg) 54 | end 55 | end 56 | 57 | ----------------------------------------------------------------------- 58 | 59 | -- Public interface 60 | 61 | ----------------------------------------------------------------------- 62 | 63 | function Interpreter:interpret() 64 | if (Flang.DEBUG_LOGGING) then 65 | print("====== PARSE TREE =====") 66 | self.tree:display(0) 67 | print("====== END TREE =======\n") 68 | end 69 | 70 | self.current_symbol_scope = Flang.Scope:new({}) 71 | return self:visit(self.tree) 72 | end 73 | 74 | ----------------------------------------------------------------------- 75 | 76 | -- State information 77 | 78 | ----------------------------------------------------------------------- 79 | 80 | function Interpreter:get_variable(variable_name) 81 | return self.current_symbol_scope:getVariable(variable_name) 82 | end 83 | 84 | function Interpreter:set_variable(variable_name, value) 85 | self.current_symbol_scope:setVariable(variable_name, value) 86 | end 87 | 88 | function Interpreter:add_method_definition(method_name, arguments, num_arguments, block) 89 | --[[ 90 | Adds a method to the global namespace 91 | 92 | By default, methods are defined on the current class, "this" 93 | Methods defined in other classes are prefixed with their class. 94 | 95 | "bar()" is just named as "bar" 96 | "Foo.baz()" is named as "Foo.baz" 97 | ]] 98 | 99 | if (method_name == nil) then 100 | self:error("Cannot define nil method name") 101 | end 102 | 103 | -- We'll construct this method using the above info, and add it to the 104 | -- global methods table at the end 105 | local method = { 106 | method_name = method_name, 107 | arguments = arguments, 108 | num_arguments = num_arguments, 109 | block = block 110 | } 111 | 112 | self.method_table_global[method_name] = method 113 | end 114 | 115 | --[[ 116 | returns -> 117 | { 118 | method_name = method_name, 119 | arguments = arguments, 120 | block = block 121 | } 122 | ]] 123 | function Interpreter:get_method(method_name) 124 | if (method_name == nil) then 125 | self:error("Cannot get method with nil name") 126 | end 127 | 128 | if (self.method_table_global[method_name] == nil) then 129 | self:error("Method lookup failed for name " .. method_name) 130 | end 131 | 132 | return self.method_table_global[method_name] 133 | end 134 | 135 | function Interpreter:get_function_method(function_class, method_name) 136 | -- hail mary of a call 137 | if (Flang.LuaFunction[function_class] == nil) then 138 | self:error("Function class lookup failed for name <" .. function_class .. ">") 139 | end 140 | 141 | if (Flang.LuaFunction[function_class][method_name] == nil) then 142 | self:error("Function class method lookup failed for name <" .. function_class .. "." .. method_name .. ">") 143 | end 144 | 145 | return Flang.LuaFunction[function_class][method_name] 146 | end 147 | 148 | ----------------------------------------------------------------------- 149 | 150 | -- AST traversal 151 | -- Every node must have a corresponding method here 152 | 153 | ----------------------------------------------------------------------- 154 | 155 | lastVisitedNode = nil 156 | function Interpreter:visit(node) 157 | if (node == nil) then 158 | self:error("Interpreter got nil node!") 159 | end 160 | 161 | -- See https://stackoverflow.com/questions/26042599/lua-call-a-function-using-its-name-string-in-a-class 162 | 163 | -- comment out this logging for faster performance 164 | local method_name = node.type 165 | if not self[method_name] then 166 | self:error("No method in interpreter with name: " .. dq(node.type)) 167 | end 168 | -- end section 169 | 170 | lastVisitedNode = node 171 | -- print("visiting " .. method_name) 172 | 173 | -- Call and return the method 174 | return self[node.type](self, node) 175 | end 176 | 177 | function Interpreter:BinOp(node) 178 | local left = self:visit(node.left) 179 | local right = self:visit(node.right) 180 | 181 | if node.token_type == Symbols.PLUS then 182 | if (Util.isString(left) or Util.isString(right)) then 183 | return tostring(left) .. tostring(right) 184 | else 185 | return left + right 186 | end 187 | elseif node.token_type == Symbols.MINUS then 188 | return left - right 189 | elseif node.token_type == Symbols.MUL then 190 | return left * right 191 | elseif node.token_type == Symbols.DIV then 192 | if (right == 0) then 193 | self:error("Division by Zero") 194 | end 195 | return left / right 196 | elseif node.token_type == Symbols.MODULUS then 197 | return left % right 198 | end 199 | end 200 | 201 | function Interpreter:LogicalOr(node) 202 | local left = self:visit(node.left) 203 | local right = self:visit(node.right) 204 | 205 | return left or right 206 | end 207 | 208 | function Interpreter:LogicalAnd(node) 209 | local left = self:visit(node.left) 210 | local right = self:visit(node.right) 211 | 212 | return left and right 213 | end 214 | 215 | function Interpreter:UnaryOp(node) 216 | if node.token_type == Symbols.PLUS then 217 | return self:visit(node.expr) 218 | elseif node.token_type == Symbols.MINUS then 219 | return -self:visit(node.expr) 220 | end 221 | end 222 | 223 | function Interpreter:Num(node) 224 | return node.parsed_value 225 | end 226 | 227 | function Interpreter:String(node) 228 | return node.parsed_value 229 | end 230 | 231 | function Interpreter:NoOp(node) 232 | -- do nothing 233 | end 234 | 235 | function Interpreter:Assign(node) 236 | local variable_name = node.left.value 237 | local token_type = node.assignment_token.type 238 | 239 | local visitedRightNode = self:visit(node.right) 240 | if (token_type == Symbols.EQUALS) then 241 | self:set_variable(variable_name, visitedRightNode) 242 | return 243 | end 244 | 245 | if (token_type == Symbols.ASSIGN_PLUS) then 246 | local assignValue 247 | local visitedLeftNode = self:get_variable(variable_name) 248 | if (Util.isString(visitedLeftNode) and Util.isString(visitedRightNode)) then 249 | assignValue = visitedLeftNode .. visitedRightNode 250 | else 251 | assignValue = visitedLeftNode + visitedRightNode 252 | end 253 | self:set_variable(variable_name, assignValue) 254 | 255 | elseif (token_type == Symbols.ASSIGN_MINUS) then 256 | self:set_variable(variable_name, self:get_variable(variable_name) - visitedRightNode) 257 | 258 | elseif (token_type == Symbols.ASSIGN_MUL) then 259 | self:set_variable(variable_name, self:get_variable(variable_name) * visitedRightNode) 260 | 261 | elseif (token_type == Symbols.ASSIGN_DIV) then 262 | local right_value = visitedRightNode 263 | if (right_value == 0) then 264 | self:error("Division by Zero") 265 | end 266 | self:set_variable(variable_name, self:get_variable(variable_name) / visitedRightNode) 267 | end 268 | end 269 | 270 | function Interpreter:ArrayAssign(node) 271 | local variable_name = node.left.value 272 | local token_type = node.assignment_token.type 273 | 274 | -- There's a table out there with our variable_name, go get it 275 | local arrayValue = self:get_variable(variable_name) 276 | if (not Util.isTable(arrayValue)) then 277 | self:error("Expected a table. Got something else") 278 | end 279 | 280 | -- The "+1" is to enable 0 indexing in Flang 281 | local indexValue = self:visit(node.indexExpr) 282 | if (Util.isNumber(indexValue)) then 283 | -- The +1 is to allow 0 indexing 284 | indexValue = indexValue + 1 285 | end 286 | local rightExprValue = self:visit(node.right) 287 | 288 | if (token_type == Symbols.EQUALS) then 289 | arrayValue[indexValue] = rightExprValue 290 | return 291 | end 292 | 293 | -- Everything in the array has already been visited! 294 | local existingValueAtIndex = arrayValue[indexValue] 295 | 296 | if (token_type == Symbols.ASSIGN_PLUS) then 297 | arrayValue[indexValue] = existingValueAtIndex + rightExprValue 298 | 299 | elseif (token_type == Symbols.ASSIGN_MINUS) then 300 | arrayValue[indexValue] = existingValueAtIndex - rightExprValue 301 | 302 | elseif (token_type == Symbols.ASSIGN_MUL) then 303 | arrayValue[indexValue] = existingValueAtIndex * rightExprValue 304 | 305 | elseif (token_type == Symbols.ASSIGN_DIV) then 306 | if (rightExprValue == 0) then 307 | self:error("Division by Zero") 308 | end 309 | arrayValue[indexValue] = existingValueAtIndex / rightExprValue 310 | end 311 | end 312 | 313 | function Interpreter:Bool(node) 314 | return node.parsed_value 315 | end 316 | 317 | function Interpreter:Var(node) 318 | return self:get_variable(node.value) 319 | end 320 | 321 | function Interpreter:Cmp(node) 322 | local left = self:visit(node.left) 323 | local right = self:visit(node.right) 324 | 325 | if node.token_type == Symbols.CMP_EQUALS then 326 | return left == right 327 | elseif node.token_type == Symbols.CMP_NEQUALS then 328 | return left ~= right 329 | elseif node.token_type == Symbols.GT then 330 | return left > right 331 | elseif node.token_type == Symbols.LT then 332 | return left < right 333 | elseif node.token_type == Symbols.GTE then 334 | return left >= right 335 | elseif node.token_type == Symbols.LTE then 336 | return left <= right 337 | end 338 | end 339 | 340 | function Interpreter:Negate(node) 341 | return not self:visit(node.expr) 342 | end 343 | 344 | function Interpreter:If(node) 345 | --[[ 346 | Only execute the block if: 347 | ("if" or "elseif") the conditional exists and is true 348 | ("else") no conditional exists 349 | ]] 350 | local should_execute_block = true 351 | 352 | if node.conditional then 353 | should_execute_block = self:visit(node.conditional) 354 | end 355 | 356 | -- If this block gets executed, then any subsequent blocks do not get executed 357 | if should_execute_block then 358 | return self:visit(node.block) 359 | else 360 | if node.next_if then 361 | return self:visit(node.next_if) 362 | end 363 | end 364 | end 365 | 366 | function Interpreter:StatementList(node) 367 | -- Our block is starting now. Change scope 368 | self.current_symbol_scope = self.current_symbol_scope:enterBlock() 369 | 370 | if (self.global_symbol_scope == nil) then 371 | -- This is the first scope ever created. Must be PROGRAM global 372 | self.global_symbol_scope = self.current_symbol_scope 373 | end 374 | 375 | -- Iterate over each of the children 376 | -- Note, this is faster than ipairs 377 | local k 378 | for k=1, node.num_children-1 do 379 | local childNode = node.children[k] 380 | 381 | -- If there's a return value, return it. Else keep going. 382 | local returnValue = self:visit(childNode) 383 | if (returnValue ~= nil) then 384 | self.current_symbol_scope = self.current_symbol_scope:exitBlock() 385 | return returnValue 386 | end 387 | end 388 | 389 | -- Scope has finished. Our block has exited. 390 | self.current_symbol_scope = self.current_symbol_scope:exitBlock() 391 | end 392 | 393 | function Interpreter:MethodDefinition(node) 394 | -- So there's a method name, the executable block, and the argument list 395 | self:add_method_definition(node.method_name, node.arguments, node.num_arguments, node.block) 396 | end 397 | 398 | function Interpreter:MethodInvocation(node) 399 | -- Get the method 400 | local method = self:get_method(node.method_name) 401 | 402 | -- Translate our arguments 403 | local method_arguments = method.arguments 404 | local invocation_arguments = node.arguments 405 | 406 | if (method.num_arguments ~= node.num_arguments) then 407 | self:error("Expected " .. method.num_arguments .. " arguments for method <" 408 | .. node.method_name .. "> but instead got " .. node.num_arguments) 409 | end 410 | 411 | -- Get the proper values for the arguments in the current scope 412 | -- THEN change scope and set them! 413 | 414 | local argumentValues = {} 415 | local k 416 | for k = 1, node.num_arguments do 417 | -- This is some expression that needs to be visited for evaluation 418 | local invocation_arg = invocation_arguments[k] 419 | argumentValues[k] = self:visit(invocation_arg) 420 | end 421 | 422 | -- Now set the arguments in the invocation scope 423 | self.current_symbol_scope = self.current_symbol_scope:enterCall() 424 | 425 | for k = 1, node.num_arguments do 426 | -- This is a Node.METHOD_ARGUMENT_TYPE 427 | local method_arg = method_arguments[k] 428 | self:set_variable(method_arg.value, argumentValues[k]) 429 | end 430 | 431 | -- Execute the block 432 | local blockReturnValue = self:visit(method.block) 433 | 434 | -- Return from our scope 435 | self.current_symbol_scope = self.current_symbol_scope:exitCall() 436 | return blockReturnValue 437 | end 438 | 439 | function Interpreter:ReturnStatement(node) 440 | return self:visit(node.expr) 441 | end 442 | 443 | function Interpreter:BreakStatement(node) 444 | return Symbols.Control.BREAK 445 | end 446 | 447 | function Interpreter:ContinueStatement(node) 448 | return Symbols.Control.CONTINUE 449 | end 450 | 451 | function Interpreter:FunctionCall(node) 452 | -- Get the function method 453 | -- note that this doesn't do chaining 454 | local method_invocation = node.method_invocation 455 | local classOrIdentifier = node.class 456 | 457 | -- If the "class" is actually a variable, then do a self class invocation 458 | -- E.g. 459 | --[[ 460 | str = "" 461 | str.length <==> String.length(str) 462 | ]] 463 | local functionMethod 464 | local visitedArguments 465 | 466 | local identifierValue = self.current_symbol_scope:getVariable(classOrIdentifier, false) 467 | if (identifierValue ~= nil) then 468 | -- This is a self call on the variable `stringVariable.length()` 469 | -- function class name is the type itself 470 | local functionType = type(identifierValue) 471 | -- functionType will be string, number, table, etc. 472 | functionMethod = self:get_function_method(functionType, method_invocation.method_name) 473 | 474 | -- The argument is just our variable 475 | visitedArguments = {identifierValue} 476 | 477 | local k 478 | for k = 1, method_invocation.num_arguments do 479 | -- This is some expression that needs to be visited for evaluation 480 | local invocation_arg = method_invocation.arguments[k] 481 | 482 | -- Since our first arg is the variable itself, we have to start our index at +1 483 | visitedArguments[k + 1] = self:visit(invocation_arg) 484 | end 485 | else 486 | functionMethod = self:get_function_method(classOrIdentifier, method_invocation.method_name) 487 | 488 | -- Each argument needs to be visited first! 489 | local k 490 | visitedArguments = {} 491 | for k = 1, method_invocation.num_arguments do 492 | -- This is some expression that needs to be visited for evaluation 493 | local invocation_arg = method_invocation.arguments[k] 494 | visitedArguments[k] = self:visit(invocation_arg) 495 | end 496 | end 497 | 498 | local functionReturnobject = functionMethod(self, self.wrapper, visitedArguments) 499 | if (functionReturnobject == nil) then 500 | return nil 501 | else 502 | -- Check if we have an error to throw 503 | if (functionReturnobject.hasError) then 504 | self:error(functionReturnobject.errorMessage) 505 | end 506 | 507 | return functionReturnobject.result 508 | end 509 | end 510 | 511 | -- Note that the array is constructed here 512 | function Interpreter:Array(node) 513 | local backingTable = {} 514 | 515 | -- Populate our table 516 | local k 517 | for k = 1, node.length do 518 | -- We have to visit everything before it reaches the array contents 519 | backingTable[k] = self:visit(node.arguments[k]) 520 | end 521 | 522 | return backingTable 523 | end 524 | 525 | function Interpreter:ArrayIndexGet(node) 526 | -- Get the variable 527 | local identifierName = node.identifier 528 | local identifierTable = self:get_variable(identifierName) 529 | 530 | local index = self:visit(node.expr) 531 | if (Util.isNumber(index)) then 532 | -- The +1 is to allow 0 indexing 533 | index = index + 1 534 | end 535 | 536 | local tableValue = identifierTable[index] 537 | if (tableValue == nil) then 538 | self:error("Array indexing error on: <" .. identifierName .. "> at index: <" .. (index - 1) .. ">") 539 | end 540 | 541 | return tableValue 542 | end 543 | 544 | ----------------------------------------------------------------------- 545 | 546 | -- For statements 547 | -- They get their own section since they're complicated 548 | 549 | ----------------------------------------------------------------------- 550 | 551 | function Interpreter:For_CollectionIteration(node) 552 | local key_iterator_variable_name = node.iteratorKeyVar 553 | local value_iterator_variable_name = node.iteratorValueVar 554 | local array_variable = self:visit(node.arrayExpr) 555 | 556 | for elementKey,elementValue in pairs(array_variable) do 557 | -- The key won't always exist 558 | if (key_iterator_variable_name ~= nil) then 559 | -- And it might just be a string ;) 560 | if (Util.isNumber(elementKey)) then 561 | -- Without this, the pairs iterator starts the array at 1 in code 562 | elementKey = elementKey - 1 563 | end 564 | self:set_variable(key_iterator_variable_name, elementKey) 565 | end 566 | self:set_variable(value_iterator_variable_name, elementValue) 567 | local returnValue = self:visit(node.block) 568 | 569 | if (returnValue ~= nil) then 570 | if (returnValue == Symbols.Control.BREAK) then 571 | -- break the loop entirely 572 | break 573 | elseif (returnValue == Symbols.Control.CONTINUE) then 574 | -- Continue execution onward. Since the StatementList has 575 | -- returned this value, we're good to just continue block execution 576 | else 577 | -- The return value is just a real return value 578 | return returnValue 579 | end 580 | end 581 | end 582 | end 583 | 584 | function Interpreter:For_Enhanced(node) 585 | self:visit(node.initializer) 586 | 587 | -- extract the variable 588 | local variable_name = node.initializer.left.value 589 | 590 | -- visit the condition value 591 | local condition_value = self:visit(node.condition) 592 | if (not Util.isNumber(condition_value)) then 593 | self:error("Expected for loop condition to evaluate to number") 594 | end 595 | 596 | local initializer_value = self:get_variable(variable_name) 597 | 598 | if (node.incrementer.type == Node.NO_OP_TYPE) then 599 | incrementer_value = 1 600 | else 601 | incrementer_value = self:visit(node.incrementer) 602 | end 603 | 604 | for i = initializer_value, (condition_value-1), incrementer_value do 605 | -- set i 606 | self:set_variable(variable_name, i) 607 | local returnValue = self:visit(node.block) 608 | 609 | if (returnValue ~= nil) then 610 | if (returnValue == Symbols.Control.BREAK) then 611 | -- break the loop entirely 612 | break 613 | elseif (returnValue == Symbols.Control.CONTINUE) then 614 | -- Continue execution onward. Since the StatementList has 615 | -- returned this value, we're good to just continue block execution 616 | else 617 | -- The return value is just a real return value 618 | return returnValue 619 | end 620 | end 621 | end 622 | end 623 | 624 | function Interpreter:For_Standard(node) 625 | self:visit(node.initializer) 626 | while self:visit(node.condition) do 627 | local returnValue = self:visit(node.block) 628 | 629 | if (returnValue ~= nil) then 630 | if (returnValue == Symbols.Control.BREAK) then 631 | -- break the loop entirely 632 | break 633 | elseif (returnValue == Symbols.Control.CONTINUE) then 634 | -- Continue execution onward. Since the StatementList has 635 | -- returned this value, we're good to just continue block execution 636 | else 637 | -- The return value is just a real return value 638 | return returnValue 639 | end 640 | end 641 | 642 | self:visit(node.incrementer) 643 | end 644 | end 645 | 646 | function Interpreter:For(node) 647 | --[[ 648 | This is either a standard for loop, an enhanced for loop, or an iteration loop. 649 | 650 | Enhanced for-loops have the following structure: 651 | for (assignment ; number (; number) ) block 652 | 653 | Without this structure, we fallback to the standard for loop 654 | ]] 655 | 656 | -- We enter scope here since the initializer is inside the forloop scope itself 657 | -- e.g. for "(i=0..." the "i" shouldn't remain in scope afterwards 658 | self.current_symbol_scope = self.current_symbol_scope:enterBlock() 659 | 660 | local returnValue 661 | if (node.isCollectionIteration) then 662 | returnValue = self:For_CollectionIteration(node) 663 | else 664 | if (node.enhanced) then 665 | returnValue = self:For_Enhanced(node) 666 | else 667 | returnValue = self:For_Standard(node) 668 | end 669 | end 670 | 671 | self.current_symbol_scope = self.current_symbol_scope:exitBlock() 672 | return returnValue 673 | end 674 | -------------------------------------------------------------------------------- /lang/base/lexer.lua: -------------------------------------------------------------------------------- 1 | Lexer = {} 2 | Flang.Lexer = Lexer 3 | Lexer.__index = Lexer 4 | 5 | function Lexer:new(o) 6 | if not o then 7 | error("nil constructor!") 8 | end 9 | 10 | o = { 11 | sourceText = o.sourceText, 12 | c1 = "", 13 | c2 = "", 14 | character = nil 15 | } 16 | -- Create a scanner and initialize it 17 | o.scanner = Flang.Scanner:new({sourceText = o.sourceText}) 18 | 19 | setmetatable(o, self) 20 | self.__index = self 21 | 22 | return o 23 | end 24 | 25 | --[[ 26 | Construct and return the next token in the text 27 | ]] 28 | function Lexer:get() 29 | if (self.character == nil) then 30 | self:getChar() 31 | end 32 | 33 | -- Read past any whitespace 34 | while Flang.Symbols.isWhitespace(self.c1) do 35 | self:getChar() 36 | end 37 | 38 | -- Consume any comments until we verifiably get to a non-comment 39 | while (self.c2 == Flang.Symbols.SINGLE_LINE_COMMENT_START) do 40 | self:getChar() 41 | 42 | -- Eat everything until the line is over 43 | while (self.c1 ~= Symbols.NEWLINE and self.c1 ~= Flang.Character.ENDMARK) do 44 | self:getChar() 45 | end 46 | 47 | -- trim up any trailing characters 48 | while Flang.Symbols.isWhitespace(self.c1) do 49 | self:getChar() 50 | end 51 | end 52 | 53 | -- Create a token and consume info! 54 | token = Flang.Token:new(self.character) 55 | 56 | if (self.c1 == Flang.Character.ENDMARK) then 57 | token.type = Flang.Symbols.EOF 58 | return token 59 | end 60 | 61 | -- parse an identifier 62 | if (Flang.Symbols.isIdentifierStartChar(self.c1)) then 63 | token.type = Flang.Symbols.IDENTIFIER 64 | self:getChar() 65 | 66 | -- get the entire identifier 67 | while (Flang.Symbols.isIdentifierChar(self.c1)) do 68 | token.cargo = token.cargo .. self.c1 69 | self:getChar() 70 | end 71 | 72 | -- check if the token is a keyword 73 | if (Flang.Symbols.isKeyword(token.cargo)) then 74 | token.type = token.cargo 75 | end 76 | 77 | return token 78 | end 79 | 80 | -- numbers 81 | if (Flang.Symbols.isNumberStartChar(self.c1)) then 82 | token.type = Flang.Symbols.NUMBER 83 | self:getChar() 84 | 85 | -- get the entire number 86 | -- TODO Make sure "." doesn't exist more than once in the number 87 | while (Flang.Symbols.isNumberChar(self.c1)) do 88 | token.cargo = token.cargo .. self.c1 89 | self:getChar() 90 | end 91 | 92 | return token 93 | end 94 | 95 | -- strings! 96 | if (Flang.Symbols.isStringStartChar(self.c1)) then 97 | -- remember the quoteChar (single or double quote) 98 | -- so we can look for the same character to terminate the quote. 99 | 100 | startingQuoteChar = self.c1 101 | self:getChar() 102 | 103 | -- consume the string contents 104 | while (self.c1 ~= startingQuoteChar) do 105 | if (self.c1 == Flang.Character.ENDMARK) then 106 | token:abort("Found end of file before end of string literal") 107 | end 108 | 109 | token.cargo = token.cargo .. self.c1 110 | self:getChar() 111 | end 112 | 113 | -- The string is done. Add the quote to finish the string 114 | token.cargo = token.cargo .. self.c1 115 | self:getChar() 116 | token.type = Flang.Symbols.STRING 117 | return token 118 | end 119 | 120 | -- two character symbols before one character symbols 121 | if (Flang.Symbols.isTwoCharacterSymbol(self.c2)) then 122 | token.cargo = self.c2 123 | -- For symbols, the token type is the same as the cargo 124 | token.type = token.cargo 125 | 126 | self:getChar() -- read past the first character of a 2-character token 127 | self:getChar() -- read past the second character of a 2-character token 128 | return token 129 | end 130 | 131 | -- one character symbols 132 | if (Flang.Symbols.isOneCharacterSymbol(self.c1)) then 133 | -- For symbols, the token type is the same as the cargo 134 | token.type = token.cargo 135 | 136 | self:getChar() -- read past the symbol 137 | return token 138 | end 139 | 140 | -- At this point, who the hell knows what got returned. Throw an error 141 | token:abort("Unknown character or symbol in lexer: " .. dq(self.c1)) 142 | end 143 | 144 | --[[ 145 | Get the next character string 146 | ]] 147 | function Lexer:getChar() 148 | self.character = self.scanner:get() 149 | 150 | -- get the first character string 151 | self.c1 = self.character.cargo 152 | 153 | -- get the concatenation of the current character and the next one 154 | self.c2 = self.c1 .. self.scanner:lookahead(1) 155 | 156 | if Flang.VERBOSE_LOGGING then 157 | self:status() 158 | end 159 | end 160 | 161 | function Lexer:status() 162 | local status = " c1: " .. dq(self.c1) .. "\tc2: " .. dq(self.c2) .. "\tchar: " .. tostring(self.character) 163 | print(status:gsub("\n", "\\n")) 164 | end 165 | -------------------------------------------------------------------------------- /lang/base/lua_functions/core.lua: -------------------------------------------------------------------------------- 1 | -- who: `core.lua` 2 | -- what: Standard library for the Flang language 3 | 4 | -- Add our class to the global namespace for lua functions 5 | -- The module name should be capitalized. It also should be 6 | -- unique, otherwise module overwriting will occur. 7 | local Core = {} 8 | Core.__index = Core 9 | Flang.LuaFunction.Core = Core 10 | -- Now start adding your functions below 11 | 12 | --[[ 13 | * "wrapper" is the flang runner (Factorio) state information for 14 | the chip itself. This argument is passed into every function call. 15 | See `FlangChip.lua` for the contents. You can expect the chip entity 16 | for example. 17 | * "flangArguments" is an array of arguments from the Flang code itself. It is 1 indexed. 18 | ]] 19 | function Core:helloWorld(wrapper, flangArguments) 20 | -- Your function can return values back to the code in a table 21 | return { 22 | -- This key is actually returned to the code 23 | result = flangArguments[1] + 1 24 | } 25 | end 26 | 27 | function Core:tick(wrapper, flangArguments) 28 | return { 29 | result = Flang.tick 30 | } 31 | end 32 | 33 | function Core:luaPrint(wrapper, flangArguments) 34 | print(Util.concat_table_to_string(flangArguments)) 35 | return nil 36 | end 37 | 38 | function Core:print(wrapper, flangArguments) 39 | local msg = Util.concat_table_to_string(flangArguments) 40 | wrapper.printer(wrapper.entity, msg, false) 41 | return nil 42 | end 43 | 44 | -- args = (index, virtual signal name suffix, signal count) 45 | function Core:writeVirtualSignal(wrapper, flangArguments) 46 | local signalType = "virtual" 47 | local entity = wrapper.entity 48 | local signalIndex = flangArguments[1] 49 | local signalName = "signal-" .. flangArguments[2] 50 | local signalCount = flangArguments[3] 51 | 52 | return writeSignal(entity, signalIndex, signalType, signalName, signalCount) 53 | end 54 | 55 | -- args = (index, signal name, signal count) 56 | function Core:writeItemSignal(wrapper, flangArguments) 57 | local signalType = "item" 58 | local entity = wrapper.entity 59 | local signalIndex = flangArguments[1] 60 | local signalCount = flangArguments[3] 61 | local signalName = flangArguments[2] 62 | 63 | return writeSignal(entity, signalIndex, signalType, signalName, signalCount) 64 | end 65 | 66 | -- args = (virtual signal name, network color) 67 | function Core:readVirtualSignal(wrapper, flangArguments) 68 | local signalType = "virtual" 69 | local entity = wrapper.entity 70 | local signalName = "signal-" .. flangArguments[1] 71 | local circuitNetworkName = flangArguments[2] 72 | 73 | return readSignal(entity, circuitNetworkName, signalType, signalName) 74 | end 75 | 76 | function Core:readItemSignal(wrapper, flangArguments) 77 | local signalType = "item" 78 | local entity = wrapper.entity 79 | local signalName = flangArguments[1] 80 | local circuitNetworkName = flangArguments[2] 81 | 82 | return readSignal(entity, circuitNetworkName, signalType, signalName) 83 | end 84 | 85 | ----------------------------------------------------------------------- 86 | 87 | -- Private functions 88 | 89 | ----------------------------------------------------------------------- 90 | 91 | -- signalType -> "item" or "virtual" 92 | function writeSignal(entity, signalIndex, signalType, signalName, signalCount) 93 | if (signalIndex < 0 or signalIndex > 100) then 94 | -- This is a game ending exception... 95 | -- Let's just soft crash! 96 | return { 97 | hasError = true, 98 | errorMessage = "Core:Write Signal cannot accept a signal index outside the bounds [0..100]" 99 | } 100 | end 101 | 102 | local combinatorBehavior = entity.get_control_behavior() 103 | combinatorBehavior.set_signal(signalIndex, { 104 | signal = { 105 | type = signalType, 106 | name = signalName 107 | }, 108 | count = signalCount 109 | }) 110 | end 111 | 112 | function readSignal(entity, circuitNetworkName, signalType, signalName) 113 | if (entity == nil) then 114 | return { 115 | hasError = true, 116 | errorMessage = "Entity is nil" 117 | } 118 | end 119 | 120 | local combinatorBehavior = entity.get_control_behavior() 121 | 122 | -- Check the network type 123 | local circuitNetworkColor 124 | if (circuitNetworkName == "red") then 125 | circuitNetworkColor = defines.wire_type.red 126 | elseif (circuitNetworkName == "green") then 127 | circuitNetworkColor = defines.wire_type.green 128 | elseif (circuitNetworkName == "copper") then 129 | circuitNetworkColor = defines.wire_type.copper 130 | else 131 | return { 132 | hasError = true, 133 | errorMessage = "Circuit network with name " .. circuitNetworkName .. " is invalid. Try ['red', 'green', 'copper']" 134 | } 135 | end 136 | 137 | local circuitNetwork = combinatorBehavior.get_circuit_network(circuitNetworkColor) 138 | if (circuitNetwork == nil) then 139 | return { 140 | hasError = true, 141 | errorMessage = "Core:Read Signal has no network attached on type: " .. circuitNetworkName 142 | } 143 | end 144 | 145 | local signal = { 146 | type = signalType, 147 | name = signalName 148 | } 149 | return { 150 | result = circuitNetwork.get_signal(signal) 151 | } 152 | end 153 | -------------------------------------------------------------------------------- /lang/base/lua_functions/string.lua: -------------------------------------------------------------------------------- 1 | -- who: `string.lua` 2 | -- what: String functions library 3 | 4 | -- Add our class to the global namespace for lua functions. 5 | -- Since this is a primitive, we leave it lowercase in the LuaFunction table 6 | local String = {} 7 | String.__index = String 8 | -- Adding both cases for ease 9 | Flang.LuaFunction.String = String 10 | Flang.LuaFunction.string = String 11 | 12 | function String:length(wrapper, flangArguments) 13 | return { 14 | result = flangArguments[1]:len() 15 | } 16 | end 17 | 18 | -- Return an array of each character in the string 19 | function String:getChars(wrapper, flangArguments) 20 | local chars = {} 21 | local str = flangArguments[1] 22 | for i in str:gmatch('.') do 23 | table.insert(chars, i) 24 | end 25 | 26 | return { 27 | result = chars 28 | } 29 | end 30 | 31 | function String:upper(wrapper, flangArguments) 32 | return { 33 | result = flangArguments[1]:upper() 34 | } 35 | end 36 | 37 | function String:lower(wrapper, flangArguments) 38 | return { 39 | result = flangArguments[1]:lower() 40 | } 41 | end 42 | 43 | function String:sub(wrapper, flangArguments) 44 | local str = flangArguments[1] 45 | local startIndex = flangArguments[2] 46 | local endIndex = flangArguments[3] 47 | 48 | local result 49 | if (endIndex) then 50 | result = str:sub(startIndex, endIndex) 51 | else 52 | result = str:sub(startIndex) 53 | end 54 | 55 | return { 56 | result = result 57 | } 58 | end 59 | -------------------------------------------------------------------------------- /lang/base/lua_functions/table.lua: -------------------------------------------------------------------------------- 1 | -- who: `table.lua` 2 | -- what: Table functions library 3 | 4 | -- Add our class to the global namespace for lua functions. 5 | -- Since this is a primitive, we leave it lowercase in the LuaFunction table 6 | local Table = {} 7 | Table.__index = Table 8 | -- Adding both cases for ease 9 | Flang.LuaFunction.Table = Table 10 | Flang.LuaFunction.table = Table 11 | 12 | function Table:length(wrapper, flangArguments) 13 | return { 14 | result = #flangArguments[1] 15 | } 16 | end 17 | 18 | function Table:append(wrapper, flangArguments) 19 | local varTable = flangArguments[1] 20 | local appendValue = flangArguments[2] 21 | table.insert(varTable, appendValue) 22 | 23 | return nil 24 | end 25 | 26 | -- Put supposedValue into tbl if tbl[key] does not already exist 27 | function Table:putIfAbsent(wrapper, flangArguments) 28 | local tbl = flangArguments[1] 29 | local key = flangArguments[2] 30 | local supposedValue = flangArguments[3] 31 | 32 | if (tbl[key] == nil) then 33 | tbl[key] = supposedValue 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lang/base/node.lua: -------------------------------------------------------------------------------- 1 | Node = {} 2 | Flang.Node = Node 3 | Node.__index = Node 4 | 5 | --[[ 6 | A Node object represents a node in the AST passed from the parser to the 7 | interpreter. 8 | 9 | Nodes only require a type. The object passed to this constructor could contains 10 | whatever the node requires. 11 | ]] 12 | function Node:new(o) 13 | if not o then 14 | error("nil constructor!") 15 | end 16 | 17 | if (o.type == nil) then 18 | error("Nodes require a type") 19 | end 20 | 21 | if (o.token ~= nil) then 22 | o.token_type = o.token.type 23 | end 24 | 25 | setmetatable(o, self) 26 | self.__index = self 27 | return o 28 | end 29 | 30 | function Node.print(msg) 31 | if (Flang.VERBOSE_LOGGING) then 32 | print(msg) 33 | end 34 | end 35 | 36 | ----------------------------------------------------------------------- 37 | -- Static node constructors 38 | ----------------------------------------------------------------------- 39 | 40 | Node.NUMBER_TYPE = "Num" 41 | function Node.Number(token) 42 | Node.print("creating num node " .. tostring(token)) 43 | return Node:new({ 44 | type = Node.NUMBER_TYPE, 45 | token = token, 46 | value = token.cargo, 47 | parsed_value = tonumber(token.cargo) 48 | }) 49 | end 50 | 51 | Node.BOOLEAN_TYPE = "Bool" 52 | function Node.Boolean(token) 53 | Node.print("creating boolean node " .. tostring(token)) 54 | return Node:new({ 55 | type = Node.BOOLEAN_TYPE, 56 | token = token, 57 | value = token.cargo, 58 | parsed_value = (token.cargo == "true") 59 | }) 60 | end 61 | 62 | Node.STRING_TYPE = "String" 63 | function Node.String(token) 64 | Node.print("creating string node " .. tostring(token)) 65 | return Node:new({ 66 | type = Node.STRING_TYPE, 67 | token = token, 68 | value = token.cargo, 69 | -- Remove the quotes lol 70 | parsed_value = token.cargo:gsub("\"", "") 71 | }) 72 | end 73 | 74 | Node.ARRAY_TYPE = "Array" 75 | function Node.ArrayConstructor(token, arguments, length) 76 | Node.print("creating array constructor node " .. tostring(token)) 77 | return Node:new({ 78 | type = Node.ARRAY_TYPE, 79 | token = token, 80 | arguments = arguments, 81 | length = length, 82 | -- Note that this table can't be created lest it be reused globally lol 83 | backing_table = nil 84 | }) 85 | end 86 | 87 | Node.VARIABLE_TYPE = "Var" 88 | function Node.Variable(token) 89 | Node.print("creating var node " .. tostring(token)) 90 | return Node:new({ 91 | type = Node.VARIABLE_TYPE, 92 | token = token, 93 | value = token.cargo 94 | }) 95 | end 96 | 97 | Node.BINARY_OPERATOR_TYPE = "BinOp" 98 | function Node.BinaryOperator(left, operator, right) 99 | Node.print("creating bin op node " .. tostring(operator)) 100 | return Node:new({ 101 | type = Node.BINARY_OPERATOR_TYPE, 102 | left = left, 103 | token = operator, 104 | right = right 105 | }) 106 | end 107 | 108 | Node.UNARY_OPERATOR_TYPE = "UnaryOp" 109 | function Node.UnaryOperator(operator, expr) 110 | Node.print("creating unary op node " .. tostring(operator)) 111 | return Node:new({ 112 | type = Node.UNARY_OPERATOR_TYPE, 113 | token = operator, 114 | expr = expr 115 | }) 116 | end 117 | 118 | Node.NO_OP_TYPE = "NoOp" 119 | function Node.NoOp() 120 | Node.print("creating no-op node") 121 | return Node:new({ 122 | type = Node.NO_OP_TYPE 123 | }) 124 | end 125 | 126 | --[[ 127 | right: the expression to the right of the operator 128 | ]] 129 | Node.ASSIGN_TYPE = "Assign" 130 | function Node.Assign(left, operator, right, assignment_token) 131 | Node.print("creating assign node: " .. dq(left) .. " and token " .. dq(left.value)) 132 | return Node:new({ 133 | type = Node.ASSIGN_TYPE, 134 | left = left, 135 | token = operator, 136 | right = right, 137 | assignment_token = assignment_token 138 | }) 139 | end 140 | 141 | Node.ARRAY_INDEX_ASSIGN_TYPE = "ArrayAssign" 142 | function Node.ArrayAssign(left, indexExpr, operator, right, assignment_token) 143 | Node.print("creating array assign node: " .. dq(left) .. " and token " .. dq(left.value)) 144 | return Node:new({ 145 | type = Node.ARRAY_INDEX_ASSIGN_TYPE, 146 | left = left, 147 | indexExpr = indexExpr, 148 | token = operator, 149 | right = right, 150 | assignment_token = assignment_token 151 | }) 152 | end 153 | 154 | Node.COMPARATOR_TYPE = "Cmp" 155 | function Node.Comparator(left, operator, right) 156 | Node.print("creating comparator node: " .. dq(operator)) 157 | return Node:new({ 158 | type = Node.COMPARATOR_TYPE, 159 | left = left, 160 | token = operator, 161 | right = right 162 | }) 163 | end 164 | 165 | Node.LOGICAL_OR_TYPE = "LogicalOr" 166 | function Node.LogicalOr(left, operator, right) 167 | Node.print("creating logical OR node: " .. dq(operator)) 168 | return Node:new({ 169 | type = Node.LOGICAL_OR_TYPE, 170 | left = left, 171 | token = operator, 172 | right = right 173 | }) 174 | end 175 | 176 | Node.LOGICAL_AND_TYPE = "LogicalAnd" 177 | function Node.LogicalAnd(left, operator, right) 178 | Node.print("creating logical AND node: " .. dq(operator)) 179 | return Node:new({ 180 | type = Node.LOGICAL_AND_TYPE, 181 | left = left, 182 | token = operator, 183 | right = right 184 | }) 185 | end 186 | 187 | Node.NEGATION_TYPE = "Negate" 188 | function Node.Negation(operator, expr) 189 | Node.print("creating negation node " .. tostring(operator)) 190 | return Node:new({ 191 | type = Node.NEGATION_TYPE, 192 | token = operator, 193 | expr = expr 194 | }) 195 | end 196 | 197 | Node.IF_TYPE = "If" 198 | function Node.If(token, conditional, block, next_if) 199 | --[[ 200 | An If statement. These nodes chain together to represent an if-elseif-else. 201 | 202 | The first "if" and subsequently chained "elseif" nodes should all contain a non-nil conditional 203 | and block. The "else" node should not contain a conditional. 204 | ]] 205 | Node.print("creating if node " .. tostring(token)) 206 | return Node:new({ 207 | type = Node.IF_TYPE, 208 | token = token, 209 | conditional = conditional, 210 | block = block, 211 | next_if = next_if 212 | }) 213 | end 214 | 215 | Node.STATEMENT_LIST_TYPE = "StatementList" 216 | function Node.StatementList() 217 | Node.print("creating StatementList node") 218 | return Node:new({ 219 | type = Node.STATEMENT_LIST_TYPE, 220 | -- As a polite aside, I fucking hate Lua so much 221 | -- No real arrays, and this joke of a substitute has to start at 1 222 | children = {}, 223 | num_children = 1 224 | }) 225 | end 226 | 227 | Node.FOR_TYPE = "For" 228 | function Node.For(token, initializer, condition, incrementer, block, enhanced, 229 | iteratorKeyVar, iteratorValueVar, arrayExpr, isCollectionIteration) 230 | Node.print("creating for node " .. tostring(token)) 231 | return Node:new({ 232 | type = Node.FOR_TYPE, 233 | token = token, 234 | initializer = initializer, 235 | condition = condition, 236 | incrementer = incrementer, 237 | block = block, 238 | enhanced = enhanced, 239 | iteratorKeyVar = iteratorKeyVar, 240 | iteratorValueVar = iteratorValueVar, 241 | arrayExpr = arrayExpr, 242 | isCollectionIteration = isCollectionIteration 243 | }) 244 | end 245 | 246 | --[[ 247 | object . method ( arguments ) 248 | 249 | the object is passed as an argument 250 | 251 | method_invocation is surprisingly a Node.METHOD_INVOCATION_TYPE 252 | ]] 253 | Node.FUNCTION_CALL_TYPE = "FunctionCall" 254 | function Node.FunctionCall(token, class, method_invocation) 255 | Node.print("creating function call node " .. tostring(token)) 256 | return Node:new({ 257 | type = Node.FUNCTION_CALL_TYPE, 258 | token = token, 259 | class = class, 260 | method_invocation = method_invocation 261 | }) 262 | end 263 | 264 | --[[ 265 | method_name . ( arguments ) 266 | | method_name . ( arguments ) . next_method_invocation 267 | 268 | arguments = { 1 = something, 2 = something else, etc. } 269 | ]] 270 | Node.METHOD_INVOCATION_TYPE = "MethodInvocation" 271 | function Node.MethodInvocation(token, method_name, arguments, num_arguments, next_method_invocation) 272 | Node.print("creating method invocation node " .. tostring(token)) 273 | return Node:new({ 274 | type = Node.METHOD_INVOCATION_TYPE, 275 | token = token, 276 | method_name = method_name, 277 | arguments = arguments, 278 | num_arguments = num_arguments, 279 | next_method_invocation = next_method_invocation 280 | }) 281 | end 282 | 283 | Node.METHOD_DEFINITION_TYPE = "MethodDefinition" 284 | function Node.MethodDefinition(token, method_name, arguments, num_arguments, block) 285 | Node.print("creating method definition node " .. tostring(token)) 286 | return Node:new({ 287 | type = Node.METHOD_DEFINITION_TYPE, 288 | token = token, 289 | method_name = method_name, 290 | arguments = arguments, 291 | num_arguments = num_arguments, 292 | block = block 293 | }) 294 | end 295 | 296 | Node.METHOD_ARGUMENT_TYPE = "MethodDefinitionArgument" 297 | function Node.MethodDefinitionArgument(token) 298 | Node.print("creating method definition argument node " .. tostring(token)) 299 | return Node:new({ 300 | type = Node.METHOD_ARGUMENT_TYPE, 301 | token = token, 302 | value = token.cargo 303 | }) 304 | end 305 | 306 | Node.RETURN_STATEMENT_TYPE = "ReturnStatement" 307 | function Node.ReturnStatement(token, expr) 308 | Node.print("creating return statement node " .. tostring(token)) 309 | return Node:new({ 310 | type = Node.RETURN_STATEMENT_TYPE, 311 | token = token, 312 | expr = expr 313 | }) 314 | end 315 | 316 | Node.BREAK_STATEMENT_TYPE = "BreakStatement" 317 | function Node.BreakStatement(token, expr) 318 | Node.print("creating break statement node " .. tostring(token)) 319 | return Node:new({ 320 | type = Node.BREAK_STATEMENT_TYPE, 321 | token = token 322 | }) 323 | end 324 | 325 | Node.CONTINUE_STATEMENT_TYPE = "ContinueStatement" 326 | function Node.ContinueStatement(token, expr) 327 | Node.print("creating continue statement node " .. tostring(token)) 328 | return Node:new({ 329 | type = Node.CONTINUE_STATEMENT_TYPE, 330 | token = token 331 | }) 332 | end 333 | 334 | Node.ARRAY_INDEX_GET_TYPE = "ArrayIndexGet" 335 | function Node.ArrayIndexGet(token, identifier, expr) 336 | Node.print("creating array index get node " .. tostring(token)) 337 | return Node:new({ 338 | type = Node.ARRAY_INDEX_GET_TYPE, 339 | token = token, 340 | identifier = identifier, 341 | expr = expr 342 | }) 343 | end 344 | 345 | ----------------------------------------------------------------------- 346 | -- Helper functions 347 | ----------------------------------------------------------------------- 348 | 349 | -- A non-recursive representation of this node 350 | function Node:__tostring() 351 | local type = self.type 352 | local m = "nodeType: " ..dq(type).. " " 353 | if (type == Node.NUMBER_TYPE or type == Node.VARIABLE_TYPE or type == Node.BOOLEAN_TYPE) then 354 | m = m .. " value: " .. dq(self.value) 355 | end 356 | 357 | if (type == Node.COMPARATOR_TYPE or type == Node.NEGATION_TYPE 358 | or type == Node.UNARY_OPERATOR_TYPE or type == Node.BINARY_OPERATOR_TYPE) then 359 | m = m .. " token " .. dq(self.token) 360 | end 361 | 362 | if (type == Node.NO_OP_TYPE) then 363 | -- pass 364 | elseif (type == Node.ASSIGN_TYPE) then 365 | m = m .. " value: " .. dq(self.left.value) 366 | elseif (type == Node.STATEMENT_LIST_TYPE) then 367 | m = m .. " num statements: " .. dq(self.num_children) 368 | elseif (type == Node.FOR_TYPE) then 369 | m = m .. " for: " .. dq(self.token) 370 | elseif (type == Node.FUNCTION_CALL_TYPE) then 371 | m = m .. " func call: " .. dq(self.token) 372 | elseif (type == Node.METHOD_INVOCATION_TYPE) then 373 | m = m .. " method invocation: " .. dq(self.token) 374 | end 375 | 376 | return m or "" 377 | end 378 | 379 | -- A recursive representation of the current node and all of it's children 380 | function Node:display(tabs, info) 381 | local tabs = tabs or 0 382 | -- Info about the tree from the parent 383 | local info = info or "" 384 | local tabString = string.rep(" ", tabs) .. info 385 | local m = tostring(self) 386 | 387 | if (self.type == Node.NUMBER_TYPE) then 388 | print(tabString .. m) 389 | 390 | elseif (self.type == Node.BOOLEAN_TYPE) then 391 | print(tabString .. "boolean: " .. dq(self.value)) 392 | 393 | elseif (self.type == Node.STRING_TYPE) then 394 | print(tabString .. "string: " .. dq(self.value)) 395 | 396 | elseif (self.type == Node.VARIABLE_TYPE) then 397 | print(tabString .. "var: " .. dq(self.value)) 398 | 399 | elseif (self.type == Node.UNARY_OPERATOR_TYPE) then 400 | print(tabString .. "unary op: " .. dq(self.token.type)) 401 | self.expr:display(tabs + 1) 402 | 403 | elseif (self.type == Node.BINARY_OPERATOR_TYPE) then 404 | print(tabString .. "bin op: " .. dq(self.token.type)) 405 | self.right:display(tabs + 1) 406 | self.left:display(tabs + 1) 407 | 408 | elseif (self.type == Node.NO_OP_TYPE) then 409 | print(tabString .. "no op") 410 | 411 | elseif (self.type == Node.ASSIGN_TYPE) then 412 | print(tabString .. "statement assign: " .. tostring(self.left.value) .. " sym: " .. dq(self.assignment_token.type)) 413 | self.right:display(tabs + 1) 414 | self.left:display(tabs + 1) 415 | 416 | elseif (self.type == Node.STATEMENT_LIST_TYPE) then 417 | print(tabString .. "STATEMENT LIST") 418 | 419 | for key,childNode in ipairs(self.children) do 420 | print(tabString .. key) 421 | childNode:display(tabs + 1) 422 | end 423 | 424 | elseif (self.type == Node.COMPARATOR_TYPE) then 425 | print(tabString .. "comparator op: " .. dq(self.token.type)) 426 | self.right:display(tabs + 1) 427 | self.left:display(tabs + 1) 428 | 429 | elseif (self.type == Node.NEGATION_TYPE) then 430 | print(tabString .. "negation: " .. dq(self.token.type)) 431 | self.expr:display(tabs + 1) 432 | 433 | elseif (self.type == Node.IF_TYPE) then 434 | print(tabString .. "if: " .. dq(self.token.type)) 435 | if self.conditional then 436 | self.conditional:display(tabs + 1, "CONDITIONAL: ") 437 | end 438 | if self.block then 439 | self.block:display(tabs + 1, "BLOCK: ") 440 | end 441 | if self.next_if then 442 | self.next_if:display(tabs + 2) 443 | end 444 | 445 | elseif (self.type == Node.FOR_TYPE) then 446 | if (self.isCollectionIteration) then 447 | print(tabString .. "for: " .. dq(self.token.type) .. " key var: " .. dq(self.iteratorKeyVar) .. " val var: " .. dq(self.iteratorValueVar) 448 | .. " collectionIteration: " .. dq(self.isCollectionIteration)) 449 | 450 | self.arrayExpr:display(tabs + 1, "ARRAY EXPR: ") 451 | else 452 | print(tabString .. "for: " .. dq(self.token.type) .. " enhanced: " .. dq(self.enhanced)) 453 | 454 | self.initializer:display(tabs + 1, "INITIALIZER: ") 455 | self.condition:display(tabs + 1, "CONDITIONAL: ") 456 | self.incrementer:display(tabs + 1, "INCREMENTER: ") 457 | end 458 | 459 | self.block:display(tabs + 2) 460 | 461 | elseif (self.type == Node.FUNCTION_CALL_TYPE) then 462 | print(tabString .. "func call on: " .. dq(self.token.cargo)) 463 | 464 | self.method_invocation:display(tabs + 1, "method: ") 465 | 466 | elseif (self.type == Node.METHOD_DEFINITION_TYPE) then 467 | print(tabString .. "method definition: " .. dq(self.method_name) .. " " .. self.num_arguments .. " args: " .. Util.set_to_string(self.arguments)) 468 | 469 | -- Recursively tell our block node to display itself 470 | -- at a (tabs + 1) deeper level 471 | self.block:display(tabs + 1, "BLOCK: ") 472 | 473 | elseif (self.type == Node.METHOD_INVOCATION_TYPE) then 474 | print(tabString .. "invocation " .. dq(self.method_name) .. " " .. self.num_arguments .. " args: " .. Util.set_to_string(self.arguments)) 475 | 476 | if self.next_method_invocation then 477 | self.next_method_invocation:display(tabs + 1, "Next method: ") 478 | end 479 | 480 | elseif (self.type == Node.RETURN_STATEMENT_TYPE) then 481 | print(tabString .. "return: " .. dq(self.token.type)) 482 | self.expr:display(tabs + 1) 483 | 484 | elseif (self.type == Node.BREAK_STATEMENT_TYPE) then 485 | print(tabString .. "break: " .. dq(self.token.type)) 486 | 487 | elseif (self.type == Node.CONTINUE_STATEMENT_TYPE) then 488 | print(tabString .. "continue: " .. dq(self.token.type)) 489 | 490 | elseif (self.type == Node.ARRAY_TYPE) then 491 | print(tabString .. "array constructor with args: " .. Util.set_to_string(self.arguments)) 492 | 493 | elseif (self.type == Node.ARRAY_INDEX_GET_TYPE) then 494 | print(tabString .. "array index get on var: " .. dq(self.identifier)) 495 | self.expr:display(tabs + 1) 496 | 497 | elseif (self.type == Node.ARRAY_INDEX_ASSIGN_TYPE) then 498 | print(tabString .. "statement array assign: " .. tostring(self.left.value) .. " sym: " .. dq(self.assignment_token.type)) 499 | self.left:display(tabs + 1, "IDENTIFIER: ") 500 | self.indexExpr:display(tabs + 1, "INDEX: ") 501 | self.right:display(tabs + 1, "ASSIGNMENT: ") 502 | 503 | elseif (self.type == Node.LOGICAL_OR_TYPE) then 504 | print(tabString .. "logical OR: " .. dq(self.token.type)) 505 | self.right:display(tabs + 1) 506 | self.left:display(tabs + 1) 507 | 508 | elseif (self.type == Node.LOGICAL_AND_TYPE) then 509 | print(tabString .. "logical AND: " .. dq(self.token.type)) 510 | self.right:display(tabs + 1) 511 | self.left:display(tabs + 1) 512 | 513 | else 514 | print("Unknown type. Can't display parse tree: " .. dq(self.type)) 515 | end 516 | end 517 | -------------------------------------------------------------------------------- /lang/base/parser.lua: -------------------------------------------------------------------------------- 1 | Parser = {} 2 | Flang.Parser = Parser 3 | Parser.__index = Parser 4 | 5 | function Parser:new(o) 6 | if not o then 7 | error("nil constructor!") 8 | end 9 | 10 | o = { 11 | lexer = o.lexer 12 | } 13 | 14 | o.current_token = o.lexer:get() 15 | o.next_token = o.lexer:get() 16 | 17 | setmetatable(o, self) 18 | self.__index = self 19 | return o 20 | end 21 | 22 | function Parser:error(msg) 23 | local errorMsg = msg .. "\nat " .. Util.set_to_string_dumb(self.current_token) 24 | local source = self.lexer.sourceText 25 | local errorLine = self.current_token.lineIndex - 1 26 | 27 | local linesMsg = Util.getLineNumbers(source, errorLine - 2, 4) 28 | error(errorMsg .. linesMsg) 29 | end 30 | 31 | --[[ 32 | 33 | Compare the current token with the input token type. If the match, then 34 | "eat" the current token and assign the next token to "self.current_token". 35 | 36 | Else, throw an exception ;) 37 | 38 | ]] 39 | function Parser:eat(token_type) 40 | if (self.current_token.type == token_type) then 41 | if (Flang.VERBOSE_LOGGING) then 42 | print("Ate token " .. dq(token_type) .. " with body: " .. dq(self.current_token)) 43 | end 44 | 45 | self.current_token = Token:copy(self.next_token) 46 | self.next_token = Token:copy(self.lexer:get()) 47 | else 48 | self:error("Expected " .. dq(token_type) .. " but got " .. dq(self.current_token.type) .. 49 | " at L" .. self.current_token.lineIndex .. ":C" .. self.current_token.columnIndex) 50 | end 51 | end 52 | 53 | --[[ 54 | Takes a token type and eats if it exists in valid_token_types 55 | ]] 56 | function Parser:eat_several(token_type, valid_token_types) 57 | if (Util.contains(valid_token_types, token_type)) then 58 | self:eat(token_type) 59 | -- print(" Ate several on token " .. dq(token_type)) 60 | -- else 61 | -- print("did not eat token "..dq(token_type)) 62 | end 63 | end 64 | 65 | ----------------------------------------------------------------------- 66 | 67 | -- AST generation 68 | 69 | ----------------------------------------------------------------------- 70 | 71 | --[[ 72 | FLANG LANGUAGE DEFINITION 73 | 74 | program : statement_list 75 | 76 | statement_list : (statement)* 77 | statement : assignment_statement 78 | | array_index_assign_statement 79 | | if_statement 80 | | (for_statement | for_collection_statement) 81 | | method_definition_statement 82 | | return_statement 83 | | break_statement 84 | | continue_statement 85 | | function_call 86 | | empty 87 | 88 | assignment_statement : variable (ASSIGN | ASSIGN_PLUS | ASSIGN_MINUS | ASSIGN_MUL | ASSIGN_DIV) expr 89 | array_index_assign_statement : variable LSQUAREBRACKET expr RSQUAREBRACKET (ASSIGN | ASSIGN_PLUS | ASSIGN_MINUS | ASSIGN_MUL | ASSIGN_DIV) expr 90 | if_statement : IF conditional block if_elseif 91 | for_statement : FOR LPAREN (for_body_standard | for_body_collection) RPAREN block 92 | method_definition_statement : DEF IDENTIFIER LPAREN (method_definition_argument COMMA | method_definition_argument)* RPAREN block 93 | return_statement : RETURN expr 94 | break_statement : BREAK 95 | continue_statement : CONTINUE 96 | empty : 97 | 98 | for_body_standard : assignment_statement SEMICOLON expr (SEMICOLON statement | SEMICOLON expr)? 99 | for_body_collection : variable (COMMA variable)? IN ARRAY 100 | 101 | if_elseif : (ELSEIF conditional block)* if_else 102 | if_else : ELSE block 103 | 104 | conditional : LPAREN expr RPAREN 105 | block : LBRACKET statement_list RBRACKET 106 | 107 | expr : expr_log_or 108 | expr_log_or : expr_log_and (LOGICAL_OR expr_log_and)* 109 | expr_log_and : expr_cmp (LOGICAL_AND expr_cmp)* 110 | expr_cmp : expr_plus ((GT | LT | GTE | LTE | CMP_EQUALS | CMP_NEQUALS) expr_plus)* 111 | expr_plus : expr_mul ((PLUS | MINUS) expr_mul)* 112 | expr_mul : factor ((MUL | DIV) factor)* 113 | factor : NEGATE factor 114 | | PLUS factor 115 | | MINUS factor 116 | | NUMBER 117 | | boolean 118 | | STRING 119 | | array_init 120 | | (variable | function_call | array_index_get) 121 | | LPAREN expr RPAREN 122 | 123 | variable : IDENTIFIER 124 | boolean : (TRUE | FALSE) 125 | 126 | array_init : LSQUAREBRACKET (expr COMMA | expr)* RSQUAREBRACKET 127 | array_index_get : IDENTIFIER LSQUAREBRACKET expr RSQUAREBRACKET 128 | 129 | function_call : (IDENTIFIER DOT)? IDENTIFIER LPAREN (argument COMMA | argument)* RPAREN 130 | argument : expr 131 | method_definition_argument : IDENTIFIER 132 | 133 | ]] 134 | 135 | --[[ 136 | http://www.cs.bilkent.edu.tr/~guvenir/courses/CS101/op_precedence.html 137 | ]] 138 | 139 | function Parser:empty() 140 | -- Intentional no-op 141 | return Node.NoOp() 142 | end 143 | 144 | ----------------------------------------------------------------------- 145 | 146 | -- Expressions 147 | 148 | ----------------------------------------------------------------------- 149 | 150 | function Parser:boolean() 151 | -- boolean : (TRUE | FALSE) 152 | local token = Token:copy(self.current_token) 153 | if (token.type == Symbols.TRUE) then 154 | self:eat(Symbols.TRUE) 155 | return Node.Boolean(token) 156 | elseif (token.type == Symbols.FALSE) then 157 | self:eat(Symbols.FALSE) 158 | return Node.Boolean(token) 159 | end 160 | end 161 | 162 | function Parser:variable(token) 163 | -- variable : IDENTIFIER 164 | local node = Node.Variable(Token:copy(self.current_token)) 165 | 166 | self:eat(Symbols.IDENTIFIER) 167 | return node 168 | end 169 | 170 | function Parser:method_definition_argument() 171 | local token = Token:copy(self.current_token) 172 | self:eat(Symbols.IDENTIFIER) 173 | return Node.MethodDefinitionArgument(token) 174 | end 175 | 176 | --[[ 177 | Gets the invocation chain 178 | ]] 179 | function Parser:method_invocation() 180 | -- start with the method identifier 181 | local token = Token:copy(self.current_token) 182 | self:eat(Symbols.IDENTIFIER) 183 | 184 | -- now onto the parens and arguments 185 | self:eat(Symbols.LPAREN) 186 | 187 | -- Now parse the arguments 188 | -- Start at 1 to iterate in LUA 189 | local num_arguments = 0 190 | local args = {} 191 | 192 | while (self.current_token.type ~= Symbols.RPAREN) do 193 | local token = Token:copy(self.current_token) 194 | -- parse the arguments 195 | if (token.type == Symbols.COMMA) then 196 | -- prep for the next argument 197 | self:eat(Symbols.COMMA) 198 | else 199 | args[#args + 1] = self:expr() 200 | num_arguments = num_arguments + 1 201 | end 202 | end 203 | 204 | self:eat(Symbols.RPAREN) 205 | 206 | local method_name = token.cargo 207 | if (self.current_token.type == Symbols.DOT) then 208 | -- This is for continued method invocations like: 209 | -- Foo.add().continued_invocation1().continued_invocation2() 210 | self:eat(Symbols.DOT) 211 | return Node.MethodInvocation(token, method_name, args, num_arguments, self:method_invocation()) 212 | else 213 | return Node.MethodInvocation(token, method_name, args, num_arguments, nil) 214 | end 215 | end 216 | 217 | function Parser:array_index_assign() 218 | -- start with the array identifier 219 | local var_token = Token:copy(self.current_token) 220 | local left = self:variable() 221 | 222 | -- now onto the index 223 | self:eat(Symbols.LSQUAREBRACKET) 224 | local indexExpr = self:expr() 225 | self:eat(Symbols.RSQUAREBRACKET) 226 | 227 | -- Now onto the rest of the statement 228 | local assignment_token = Token:copy(self.current_token) 229 | valid_tokens = Util.Set{Symbols.EQUALS, Symbols.ASSIGN_PLUS, 230 | Symbols.ASSIGN_MINUS, Symbols.ASSIGN_MUL, Symbols.ASSIGN_DIV} 231 | self:eat_several(self.current_token.type, valid_tokens) 232 | 233 | local right = self:expr() 234 | return Node.ArrayAssign(left, indexExpr, var_token, right, assignment_token) 235 | end 236 | 237 | function Parser:array_index_get() 238 | -- start with the array identifier 239 | local token = Token:copy(self.current_token) 240 | self:eat(Symbols.IDENTIFIER) 241 | 242 | -- now onto the index 243 | self:eat(Symbols.LSQUAREBRACKET) 244 | 245 | -- There's some expr inside the [] 246 | local expr = self:expr() 247 | 248 | self:eat(Symbols.RSQUAREBRACKET) 249 | return Node.ArrayIndexGet(token, token.cargo, expr) 250 | end 251 | 252 | function Parser:function_invocation() 253 | -- This is a `Foo.method()` type call 254 | -- firstIdentifier is the object 255 | local firstIdentifier = Token:copy(self.current_token) 256 | self:eat(Symbols.IDENTIFIER) 257 | self:eat(Symbols.DOT) 258 | 259 | return Node.FunctionCall(firstIdentifier, firstIdentifier.cargo, self:method_invocation()) 260 | end 261 | 262 | function Parser:array_constructor() 263 | -- start with the bracket 264 | local token = Token:copy(self.current_token) 265 | self:eat(Symbols.LSQUAREBRACKET) 266 | 267 | -- Now parse the arguments 268 | -- Start at 1 to iterate in LUA 269 | local length = 0 270 | local args = {} 271 | 272 | while (self.current_token.type ~= Symbols.RSQUAREBRACKET) do 273 | local token = Token:copy(self.current_token) 274 | -- parse the arguments 275 | if (token.type == Symbols.COMMA) then 276 | -- prep for the next argument 277 | self:eat(Symbols.COMMA) 278 | else 279 | args[#args + 1] = self:expr() 280 | length = length + 1 281 | end 282 | end 283 | 284 | self:eat(Symbols.RSQUAREBRACKET) 285 | 286 | -- return a constructor node 287 | return Node.ArrayConstructor(token, args, length) 288 | end 289 | 290 | function Parser:factor() 291 | local token = Token:copy(self.current_token) 292 | 293 | if (token.type == Symbols.NUMBER) then 294 | -- NUMBER 295 | self:eat(Symbols.NUMBER) 296 | return Node.Number(token) 297 | 298 | elseif (token.type == Symbols.PLUS) then 299 | -- ( PLUS ) factor 300 | self:eat(Symbols.PLUS) 301 | return Node.UnaryOperator(token, self:factor()) 302 | 303 | elseif (token.type == Symbols.MINUS) then 304 | -- ( MINUS ) factor 305 | self:eat(Symbols.MINUS) 306 | return Node.UnaryOperator(token, self:factor()) 307 | 308 | elseif (token.type == Symbols.NEGATE) then 309 | self:eat(Symbols.NEGATE) 310 | return Node.Negation(token, self:factor()) 311 | 312 | elseif (token.type == Symbols.TRUE or token.type == Symbols.FALSE) then 313 | return self:boolean() 314 | 315 | elseif (token.type == Symbols.STRING) then 316 | self:eat(Symbols.STRING) 317 | return Node.String(token) 318 | 319 | elseif (token.type == Symbols.LSQUAREBRACKET) then 320 | -- Array construction 321 | return self:array_constructor() 322 | 323 | elseif (token.type == Symbols.IDENTIFIER) then 324 | -- Do a lookahead. This identifier can't be assigned just yet 325 | 326 | if (self.next_token.type == Symbols.DOT) then 327 | -- This is a `Foo.method()` type call 328 | return self:function_invocation() 329 | 330 | elseif (self.next_token.type == Symbols.LPAREN) then 331 | -- This is just a `method()` call 332 | return self:method_invocation() 333 | 334 | elseif (self.next_token.type == Symbols.LSQUAREBRACKET) then 335 | -- This is an array indexing call `arr[i]` 336 | return self:array_index_get() 337 | 338 | else 339 | -- just a regular variable 340 | return self:variable() 341 | end 342 | 343 | self:error("Expected a variable or object call or method invocation. Or something idk cmon.") 344 | 345 | elseif (token.type == Symbols.LPAREN) then 346 | -- ( expr ) 347 | self:eat(Symbols.LPAREN) 348 | local node = self:expr() 349 | self:eat(Symbols.RPAREN) 350 | return node 351 | 352 | else 353 | self:error("Error, Parser.factor() has cannot continue. Are you trying to call a method on a primitive? Did you not close a brace somewhere? File an issue at https://github.com/radixdev/flang/issues") 354 | end 355 | end 356 | 357 | function Parser:expr_mul() 358 | local node = self:factor() 359 | 360 | while (self.current_token.type == Symbols.MUL 361 | or self.current_token.type == Symbols.MODULUS 362 | or self.current_token.type == Symbols.DIV) do 363 | local token = Token:copy(self.current_token) 364 | if (token.type == Symbols.MUL) then 365 | self:eat(Symbols.MUL) 366 | elseif (token.type == Symbols.DIV) then 367 | self:eat(Symbols.DIV) 368 | elseif (token.type == Symbols.MODULUS) then 369 | self:eat(Symbols.MODULUS) 370 | end 371 | 372 | -- recursively build up the AST 373 | node = Node.BinaryOperator(node, token, self:factor()) 374 | end 375 | 376 | return node 377 | end 378 | 379 | function Parser:expr_plus() 380 | local node = self:expr_mul() 381 | 382 | while (self.current_token.type == Symbols.PLUS or self.current_token.type == Symbols.MINUS) do 383 | local token = Token:copy(self.current_token) 384 | if (token.type == Symbols.PLUS) then 385 | self:eat(Symbols.PLUS) 386 | elseif (token.type == Symbols.MINUS) then 387 | self:eat(Symbols.MINUS) 388 | end 389 | 390 | -- recursively build up the AST 391 | node = Node.BinaryOperator(node, token, self:expr_mul()) 392 | end 393 | 394 | return node 395 | end 396 | 397 | function Parser:expr_cmp() 398 | -- Comparators 399 | local node = self:expr_plus() 400 | 401 | while (self.current_token.type == Symbols.GT or self.current_token.type == Symbols.LT 402 | or self.current_token.type == Symbols.GTE or self.current_token.type == Symbols.LTE 403 | or self.current_token.type == Symbols.CMP_EQUALS or self.current_token.type == Symbols.CMP_NEQUALS) do 404 | 405 | local token = Token:copy(self.current_token) 406 | if (token.type == Symbols.GT) then 407 | self:eat(Symbols.GT) 408 | elseif (token.type == Symbols.LT) then 409 | self:eat(Symbols.LT) 410 | elseif (token.type == Symbols.GTE) then 411 | self:eat(Symbols.GTE) 412 | elseif (token.type == Symbols.LTE) then 413 | self:eat(Symbols.LTE) 414 | elseif (token.type == Symbols.CMP_EQUALS) then 415 | self:eat(Symbols.CMP_EQUALS) 416 | elseif (token.type == Symbols.CMP_NEQUALS) then 417 | self:eat(Symbols.CMP_NEQUALS) 418 | end 419 | 420 | node = Node.Comparator(node, token, self:expr_plus()) 421 | end 422 | 423 | return node 424 | end 425 | 426 | function Parser:expr_log_and() 427 | local node = self:expr_cmp() 428 | 429 | while (self.current_token.type == Symbols.LOGICAL_AND) do 430 | local token = Token:copy(self.current_token) 431 | if (token.type == Symbols.LOGICAL_AND) then 432 | self:eat(Symbols.LOGICAL_AND) 433 | end 434 | 435 | node = Node.LogicalAnd(node, token, self:expr_cmp()) 436 | end 437 | 438 | return node 439 | end 440 | 441 | function Parser:expr_log_or() 442 | local node = self:expr_log_and() 443 | 444 | while (self.current_token.type == Symbols.LOGICAL_OR) do 445 | local token = Token:copy(self.current_token) 446 | if (token.type == Symbols.LOGICAL_OR) then 447 | self:eat(Symbols.LOGICAL_OR) 448 | end 449 | 450 | node = Node.LogicalOr(node, token, self:expr_log_and()) 451 | end 452 | 453 | return node 454 | end 455 | 456 | function Parser:expr() 457 | return self:expr_log_or() 458 | end 459 | 460 | ----------------------------------------------------------------------- 461 | 462 | -- Conditionals and if branching 463 | 464 | ----------------------------------------------------------------------- 465 | 466 | function Parser:conditional() 467 | if (self.current_token.type == Symbols.LPAREN) then 468 | self:eat(Symbols.LPAREN) 469 | local node = self:expr() 470 | self:eat(Symbols.RPAREN) 471 | return node 472 | end 473 | end 474 | 475 | function Parser:if_else() 476 | -- if_else : (ELSE block)? 477 | if (self.current_token.type == Symbols.ELSE) then 478 | local token = Token:copy(self.current_token) 479 | self:eat(Symbols.ELSE) 480 | return Node.If(token, nil, self:block(), nil) 481 | end 482 | end 483 | 484 | function Parser:if_elseif() 485 | -- if_elseif : (ELSEIF conditional block)* if_else 486 | local node 487 | 488 | if (self.current_token.type == Symbols.ELSE) then 489 | return self:if_else() 490 | end 491 | 492 | while (self.current_token.type == Symbols.ELSEIF) do 493 | local token = Token:copy(self.current_token) 494 | 495 | self:eat(Symbols.ELSEIF) 496 | local cond = self:conditional() 497 | local block = self:block() 498 | 499 | node = Node.If(token, cond, block, self:if_elseif()) 500 | end 501 | 502 | return node 503 | end 504 | 505 | ----------------------------------------------------------------------- 506 | 507 | -- Statements 508 | 509 | ----------------------------------------------------------------------- 510 | 511 | function Parser:block() 512 | if (self.current_token.type == Symbols.LBRACKET) then 513 | self:eat(Symbols.LBRACKET) 514 | local node = self:statement_list() 515 | self:eat(Symbols.RBRACKET) 516 | return node 517 | end 518 | end 519 | 520 | function Parser:assignment_statement() 521 | --[[ 522 | 523 | assignment_statement : variable (ASSIGN | ASSIGN_PLUS | ASSIGN_MINUS | ASSIGN_MUL | ASSIGN_DIV) expr 524 | 525 | ]] 526 | 527 | local var_token = Token:copy(self.current_token) 528 | 529 | local left = self:variable() 530 | 531 | local assignment_token = Token:copy(self.current_token) 532 | valid_tokens = Util.Set{Symbols.EQUALS, Symbols.ASSIGN_PLUS, 533 | Symbols.ASSIGN_MINUS, Symbols.ASSIGN_MUL, Symbols.ASSIGN_DIV} 534 | self:eat_several(self.current_token.type, valid_tokens) 535 | 536 | local right = self:expr() 537 | 538 | return Node.Assign(left, var_token, right, assignment_token) 539 | end 540 | 541 | function Parser:if_statement() 542 | --[[ 543 | 544 | if_statement : IF conditional block if_elseif 545 | 546 | ]] 547 | 548 | if (self.current_token.type == Symbols.IF) then 549 | local token = Token:copy(self.current_token) 550 | 551 | self:eat(Symbols.IF) 552 | local cond = self:conditional() 553 | local block = self:block() 554 | 555 | node = Node.If(token, cond, block, self:if_elseif()) 556 | 557 | return node 558 | end 559 | end 560 | 561 | function Parser:for_statement() 562 | --[[ 563 | 564 | for_statement : FOR LPAREN (for_body_standard | for_body_collection) RPAREN block 565 | for_body_standard : assignment_statement SEMICOLON expr (SEMICOLON statement | SEMICOLON expr)? 566 | for_body_collection : variable IN ARRAY 567 | 568 | ]] 569 | 570 | if (self.current_token.type == Symbols.FOR) then 571 | local token = Token:copy(self.current_token) 572 | self:eat(Symbols.FOR) 573 | self:eat(Symbols.LPAREN) 574 | 575 | local initializer 576 | local incrementer 577 | local arrayExpr 578 | local enhanced 579 | local condition 580 | local isCollectionIteration 581 | local iteratorKeyVar 582 | local iteratorValueVar 583 | 584 | -- This could be a iterator type loop `for (i in array)` 585 | -- The next token would be `in` or a comma 586 | local nextTokenType = self.next_token.type 587 | if (self.current_token.type == Symbols.IDENTIFIER and nextTokenType == Symbols.IN) then 588 | isCollectionIteration = true 589 | -- Get the iterator variable 590 | token = self.current_token 591 | self:eat(Symbols.IDENTIFIER) 592 | self:eat(Symbols.IN) 593 | 594 | -- Get the collection 595 | arrayExpr = self:expr() 596 | 597 | iteratorValueVar = token.cargo 598 | elseif (self.current_token.type == Symbols.IDENTIFIER and nextTokenType == Symbols.COMMA) then 599 | print("yeet") 600 | isCollectionIteration = true 601 | 602 | -- Get the iterator key variable 603 | token = self.current_token 604 | iteratorKeyVar = token.cargo 605 | self:eat(Symbols.IDENTIFIER) 606 | 607 | -- Get the value variable 608 | self:eat(Symbols.COMMA) 609 | iteratorValueVar = self.current_token.cargo 610 | self:eat(Symbols.IDENTIFIER) 611 | 612 | -- Now for the rest of the for statement 613 | self:eat(Symbols.IN) 614 | 615 | -- Get the collection 616 | arrayExpr = self:expr() 617 | else 618 | -- standard for body 619 | initializer = self:statement() 620 | self:eat(Symbols.SEMICOLON) 621 | 622 | condition = self:expr() 623 | 624 | --[[ 625 | the incrementer is either a number or empty (ENHANCED FOR) or a statement (STANDARD FOR) 626 | the incrementer can be an expression in the case of: 627 | for (i=0; 10; 2) { 628 | *BLOCK* 629 | } 630 | ]] 631 | incrementer = self:empty() 632 | enhanced = false 633 | if (self.current_token.type == Symbols.SEMICOLON) then 634 | self:eat(Symbols.SEMICOLON) 635 | 636 | incrementer = self:statement() 637 | if (incrementer.type == Node.NO_OP_TYPE) then 638 | -- no statement, check for an expression 639 | incrementer = self:expr() 640 | 641 | if (incrementer.type ~= Node.NO_OP_TYPE) then 642 | -- enhanced loop 643 | enhanced = true 644 | end 645 | end 646 | else 647 | enhanced = true 648 | end 649 | end 650 | 651 | self:eat(Symbols.RPAREN) 652 | local block = self:block() 653 | return Node.For(token, initializer, condition, incrementer, block, enhanced, 654 | iteratorKeyVar, iteratorValueVar, arrayExpr, isCollectionIteration) 655 | end 656 | end 657 | 658 | function Parser:method_definition_statement() 659 | --[[ 660 | 661 | method_definition_statement : DEF IDENTIFIER LPAREN method_arguments RPAREN block 662 | 663 | method_arguments : (IDENTIFIER COMMA | IDENTIFIER)* 664 | 665 | ]] 666 | 667 | -- Check if we actually have a method def 668 | if (self.current_token.type == Symbols.DEF) then 669 | local token = Token:copy(self.current_token) 670 | self:eat(Symbols.DEF) 671 | 672 | -- method name is just an identifier, so we'll eat it after 673 | local method_name = self.current_token.cargo 674 | self:eat(Symbols.IDENTIFIER) 675 | self:eat(Symbols.LPAREN) 676 | 677 | -- Parse the arguments 678 | -- Start at 1 to iterate in LUA 679 | local num_arguments = 0 680 | local arguments = {} 681 | 682 | while (self.current_token.type ~= Symbols.RPAREN) do 683 | local token = Token:copy(self.current_token) 684 | -- parse the arguments 685 | if (token.type == Symbols.COMMA) then 686 | -- prep for the next argument 687 | self:eat(Symbols.COMMA) 688 | else 689 | arguments[#arguments + 1] = self:method_definition_argument() 690 | num_arguments = num_arguments + 1 691 | end 692 | end 693 | 694 | self:eat(Symbols.RPAREN) 695 | local block = self:block() 696 | 697 | -- All done, now return a definition node 698 | return Node.MethodDefinition(token, method_name, arguments, num_arguments, block) 699 | end 700 | end 701 | 702 | function Parser:return_statement() 703 | -- return_statement : RETURN expr 704 | if (self.current_token.type == Symbols.RETURN) then 705 | local token = Token:copy(self.current_token) 706 | self:eat(Symbols.RETURN) 707 | 708 | local expr = self:expr() 709 | return Node.ReturnStatement(token, expr) 710 | end 711 | end 712 | 713 | function Parser:break_statement() 714 | if (self.current_token.type == Symbols.BREAK) then 715 | local token = Token:copy(self.current_token) 716 | self:eat(Symbols.BREAK) 717 | return Node.BreakStatement(token) 718 | end 719 | end 720 | 721 | function Parser:continue_statement() 722 | if (self.current_token.type == Symbols.CONTINUE) then 723 | local token = Token:copy(self.current_token) 724 | self:eat(Symbols.CONTINUE) 725 | return Node.ContinueStatement(token) 726 | end 727 | end 728 | 729 | function Parser:statement() 730 | --[[ 731 | 732 | statement : assignment_statement 733 | | if_statement 734 | | for_statement 735 | | method_definition_statement 736 | | return_statement 737 | | function_call 738 | | empty 739 | 740 | ]] 741 | 742 | local token = self.current_token 743 | local nextToken = self.next_token 744 | if (token.type == Symbols.IDENTIFIER) then 745 | -- Do a lookahead. This identifier can't be assigned just yet 746 | 747 | if (nextToken.type == Symbols.DOT) then 748 | return self:function_invocation() 749 | 750 | elseif (nextToken.type == Symbols.LPAREN) then 751 | -- This is just a `method()` call 752 | return self:method_invocation() 753 | 754 | elseif (nextToken.type == Symbols.LSQUAREBRACKET) then 755 | -- This is an array set `array[index] = blah` 756 | return self:array_index_assign() 757 | 758 | else 759 | return self:assignment_statement() 760 | end 761 | 762 | elseif (token.type == Symbols.IF) then 763 | node = self:if_statement() 764 | 765 | elseif (token.type == Symbols.FOR) then 766 | node = self:for_statement() 767 | 768 | elseif (token.type == Symbols.DEF) then 769 | node = self:method_definition_statement() 770 | 771 | elseif (token.type == Symbols.RETURN) then 772 | node = self:return_statement() 773 | 774 | elseif (token.type == Symbols.BREAK) then 775 | node = self:break_statement() 776 | 777 | elseif (token.type == Symbols.CONTINUE) then 778 | node = self:continue_statement() 779 | 780 | else 781 | node = self:empty() 782 | end 783 | 784 | return node 785 | end 786 | 787 | function Parser:statement_list() 788 | --[[ 789 | 790 | statement_list : (statement)* 791 | 792 | ]] 793 | local parentNode = Node.StatementList() 794 | 795 | -- parse as long as we can 796 | while self.current_token.type ~= Symbols.EOF do 797 | local node = self:statement() 798 | 799 | -- If no valid statement can be found, then break out of the statement list 800 | if (node.type == Node.NO_OP_TYPE) then 801 | -- print("No valid statement found. Exiting parser.") 802 | break 803 | end 804 | 805 | local count = parentNode.num_children 806 | parentNode.children[count] = node 807 | parentNode.num_children = count + 1 808 | end 809 | 810 | -- If there's only 1 statement in the list, only return the 1 statement 811 | -- if (parentNode.num_children == 2) then 812 | -- return parentNode.children[1] 813 | -- end 814 | 815 | return parentNode 816 | end 817 | 818 | function Parser:program() 819 | --[[ 820 | program : statement_list 821 | ]] 822 | 823 | return self:statement_list() 824 | end 825 | 826 | ----------------------------------------------------------------------- 827 | 828 | -- Public interface 829 | 830 | ----------------------------------------------------------------------- 831 | 832 | function Parser:parse() 833 | return self:program() 834 | end 835 | -------------------------------------------------------------------------------- /lang/base/scanner.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | A Scanner object reads through the sourceText 3 | and returns one character at a time. 4 | --]] 5 | Scanner = {} 6 | Flang.Scanner = Scanner 7 | Scanner.__index = Scanner 8 | 9 | 10 | --[[ 11 | *args* 12 | (string) sourceText - the entire text 13 | --]] 14 | function Scanner:new(o) 15 | if not o then 16 | error("nil constructor!") 17 | end 18 | 19 | o = { 20 | sourceText = o.sourceText, 21 | lastIndex = string.len(o.sourceText) - 1, 22 | sourceIndex = 0, 23 | -- Lines should start at 1 24 | lineIndex = 1, 25 | columnIndex = -1 26 | } 27 | 28 | setmetatable(o, self) 29 | self.__index = self 30 | return o 31 | end 32 | 33 | --[[ 34 | *return* 35 | (string) - Returns the next character in sourceText. 36 | --]] 37 | function Scanner:get() 38 | -- increment the source index 39 | self.sourceIndex = self.sourceIndex + 1 40 | 41 | -- maintain the line count 42 | if (self.sourceIndex > 0) then 43 | if (self:getChar(self.sourceIndex - 1) == "\n") then 44 | -- The previous character was a newline. 45 | -- reset the column and increment the line index 46 | self.lineIndex = self.lineIndex + 1 47 | self.columnIndex = 0 48 | end 49 | end 50 | 51 | -- scan the line 52 | self.columnIndex = self.columnIndex + 1 53 | 54 | if (self.sourceIndex > self.lastIndex) then 55 | -- We've read past the end sourceText 56 | -- return ENDMARK 57 | character = Flang.Character:new({cargo = Flang.Character.ENDMARK, 58 | sourceIndex = self.sourceIndex, lineIndex = self.lineIndex, 59 | columnIndex = self.columnIndex 60 | }) 61 | else 62 | charAtIndex = self:getChar(self.sourceIndex) 63 | character = Flang.Character:new({cargo = charAtIndex, 64 | sourceIndex = self.sourceIndex, lineIndex = self.lineIndex, 65 | columnIndex = self.columnIndex 66 | }) 67 | end 68 | 69 | return character 70 | end 71 | 72 | function Scanner:getChar(index) 73 | return string.sub(self.sourceText, index, index) 74 | end 75 | 76 | --[[ 77 | Returns the string (not character) at some offset from the current index 78 | ]] 79 | function Scanner:lookahead(offset) 80 | -- get the offet 81 | index = self.sourceIndex + offset 82 | 83 | if (index > self.lastIndex) then 84 | -- read past the end of the text, EOF 85 | return Flang.Character.ENDMARK 86 | else 87 | return self:getChar(index) 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /lang/base/scope.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Implements scope for the Interpreter. 4 | 5 | We're creating a tree of scopes to facilitate variable lookup and scope 6 | backtracking. 7 | 8 | ]] 9 | 10 | Scope = {} 11 | Flang.Scope = Scope 12 | Scope.__index = Scope 13 | 14 | function Scope:new(o) 15 | if not o then 16 | error("nil constructor!") 17 | end 18 | 19 | -- Each "ptr" points to another Scope object 20 | -- The root scope, GLOBAL, has no pointers. Any attempt to resolve further up its 21 | -- lookup chain should logically fail since GLOBAL scope is where the buck stops. 22 | o = { 23 | -- The next scope up the lookup chain within the same block. 24 | parent_scope_ptr = o.parent_scope_ptr or nil, 25 | 26 | -- The scope to resume after an invocation has returned. Functions start new scope blocks by definition! 27 | -- This is read when determining what scope to use after a block is finished. 28 | call_ptr = o.call_ptr or nil, 29 | 30 | -- The parent scope of our block. For example, if we reach the end of our lookup chain for a 31 | -- method, then we'd need to continue the lookup at the global scope. 32 | -- The parent_scope_ptr should be nil when this gets followed. 33 | block_start_ptr = o.block_start_ptr or nil, 34 | 35 | -- The whole point of scoping ... the variables! 36 | variable_table = {} 37 | } 38 | 39 | setmetatable(o, self) 40 | self.__index = self 41 | 42 | return o 43 | end 44 | 45 | function Scope:error(msg) 46 | error(msg) 47 | end 48 | 49 | function Scope:debugPrint(msg) 50 | -- print(msg .. " -> " .. Util.set_to_string_dumb(self) .. " tbl " .. Util.set_to_string_dumb(self.variable_table)) 51 | end 52 | 53 | -- A block has been entered. Our scope gets 1 level deeper. 54 | function Scope:enterBlock() 55 | self:debugPrint("enterBlock") 56 | local nextParent = self 57 | 58 | -- Determine our block start ptr 59 | local nextBlockStart 60 | if (self.block_start_ptr == nil) then 61 | -- This must be the global scope. Block lookup will continue here 62 | nextBlockStart = self 63 | else 64 | -- It's already defined. Just continue the chain 65 | nextBlockStart = self.block_start_ptr 66 | end 67 | 68 | -- And now for our call ptr 69 | local nextCallPtr 70 | if (self.call_ptr == nil) then 71 | -- There isn't a call block to return to. Just nil and move on 72 | nextCallPtr = nil 73 | else 74 | -- There's a call somewhere behind us. Continue pointing to it 75 | nextCallPtr = self.call_ptr 76 | end 77 | 78 | return Scope:new({ 79 | parent_scope_ptr = nextParent, 80 | block_start_ptr = nextBlockStart, 81 | call_ptr = nextCallPtr 82 | }) 83 | end 84 | 85 | -- A block has been exited. Our scope goes up 1 level. 86 | function Scope:exitBlock() 87 | self:debugPrint("exitBlock") 88 | -- Since the scope tree is static, we just have to return a previously linked node 89 | 90 | if (self.parent_scope_ptr ~= nil) then 91 | -- We're just normally exiting our block's scope and returning to our parent. 92 | return self.parent_scope_ptr 93 | end 94 | 95 | if (self.call_ptr ~= nil) then 96 | -- Return wasn't called, but no matter, our block's execution has ended. 97 | return self.call_ptr 98 | end 99 | 100 | self:error("ExitBlock called but no parent scope is available.") 101 | end 102 | 103 | function Scope:enterCall() 104 | self:debugPrint("enterCall") 105 | 106 | -- There's no parent here. Lookup from here should continue from the block_start_ptr instead. 107 | local nextParent = nil 108 | local nextBlockStart = self.block_start_ptr 109 | 110 | -- Since we've just entered a call block, the call_ptr should point to the Scope we just left 111 | local nextCallPtr = self 112 | 113 | return Scope:new({ 114 | parent_scope_ptr = nextParent, 115 | block_start_ptr = nextBlockStart, 116 | call_ptr = nextCallPtr 117 | }) 118 | end 119 | 120 | function Scope:exitCall() 121 | self:debugPrint("exitCall") 122 | 123 | -- The block has ended via explicit return. The call ptr should be followed 124 | -- to determine which scope to return 125 | if (self.call_ptr ~= nil) then 126 | return self.call_ptr 127 | end 128 | 129 | self:error("exitCall called but no call_ptr was available") 130 | end 131 | 132 | -- Returns the scope that contains the variable with "name" 133 | function Scope:getContainerScopeForVariable(name) 134 | -- print("Variable lookup on " .. name .. " on " .. Util.set_to_string_dumb(self) .. 135 | -- " with table " .. Util.set_to_string_dumb(self.variable_table)) 136 | -- Check ourselves! 137 | if (self.variable_table[name] ~= nil) then 138 | return self 139 | end 140 | 141 | -- Check our parent 142 | if (self.parent_scope_ptr ~= nil) then 143 | return self.parent_scope_ptr:getContainerScopeForVariable(name) 144 | end 145 | 146 | -- Calls have higher priority than the block start! 147 | -- Check the call return 148 | if (self.call_ptr ~= nil) then 149 | return self.call_ptr:getContainerScopeForVariable(name) 150 | end 151 | 152 | -- Check the block start 153 | if (self.block_start_ptr ~= nil) then 154 | return self.block_start_ptr:getContainerScopeForVariable(name) 155 | end 156 | 157 | return nil 158 | end 159 | 160 | function Scope:getVariable(name, throw_undefined_error) 161 | if (throw_undefined_error == nil) then 162 | throw_undefined_error = true 163 | end 164 | local containerScope = self:getContainerScopeForVariable(name) 165 | 166 | if (containerScope == nil) then 167 | -- No scope exists for our variable 168 | if (throw_undefined_error) then 169 | self:error("Variable lookup failed for name <" .. name .. ">. Undefined.") 170 | else 171 | return nil 172 | end 173 | end 174 | 175 | return containerScope.variable_table[name] 176 | end 177 | 178 | function Scope:setVariable(name, value) 179 | self:debugPrint("Calling set for " .. name) 180 | local containerScope = self:getContainerScopeForVariable(name) 181 | 182 | if (containerScope == nil) then 183 | -- No scope exists for our variable. Thus WE are the proper scope! 184 | containerScope = self 185 | self:debugPrint(" no scope found for " .. name .. " on set. WE ARE SCOPE!") 186 | end 187 | 188 | containerScope.variable_table[name] = value 189 | end 190 | -------------------------------------------------------------------------------- /lang/base/symbols.lua: -------------------------------------------------------------------------------- 1 | Symbols = {} 2 | Flang.Symbols = Symbols 3 | Symbols.__index = Symbols 4 | 5 | Symbols.TRUE = "true" 6 | Symbols.FALSE = "false" 7 | Symbols.IF = "if" 8 | Symbols.ELSEIF = "elseif" 9 | Symbols.ELSE = "else" 10 | Symbols.FOR = "for" 11 | Symbols.DEF = "def" 12 | Symbols.RETURN = "return" 13 | Symbols.IN = "in" 14 | Symbols.BREAK = "break" 15 | Symbols.CONTINUE = "continue" 16 | 17 | Symbols.KEYWORDS = Flang.Util.Set{ 18 | Symbols.IF, 19 | Symbols.ELSE, 20 | Symbols.ELSEIF, 21 | 22 | Symbols.FOR, 23 | Symbols.IN, 24 | 25 | Symbols.DEF, 26 | 27 | Symbols.RETURN, 28 | Symbols.BREAK, 29 | Symbols.CONTINUE, 30 | 31 | Symbols.TRUE, 32 | Symbols.FALSE 33 | } 34 | 35 | Symbols.PLUS = "+" 36 | Symbols.MINUS = "-" 37 | Symbols.MUL = "*" 38 | Symbols.DIV = "/" 39 | Symbols.LPAREN = "(" 40 | Symbols.RPAREN = ")" 41 | Symbols.EQUALS = "=" 42 | Symbols.GT = ">" 43 | Symbols.LT = "<" 44 | Symbols.NEGATE = "!" 45 | Symbols.MODULUS = "%" 46 | Symbols.LBRACKET = "{" 47 | Symbols.RBRACKET = "}" 48 | Symbols.SEMICOLON = ";" 49 | Symbols.COMMA = "," 50 | Symbols.DOT = "." 51 | Symbols.LSQUAREBRACKET = "[" 52 | Symbols.RSQUAREBRACKET = "]" 53 | 54 | Symbols.ONE_CHARACTER_SYMBOLS = Flang.Util.Set{ 55 | Symbols.EQUALS, 56 | Symbols.LPAREN, Symbols.RPAREN, 57 | Symbols.LBRACKET, Symbols.RBRACKET, 58 | Symbols.LT, Symbols.GT, 59 | Symbols.DIV, Symbols.MUL, Symbols.PLUS, Symbols.MINUS, 60 | Symbols.NEGATE, 61 | Symbols.MODULUS, 62 | Symbols.SEMICOLON, 63 | Symbols.COMMA, 64 | Symbols.DOT, 65 | Symbols.LSQUAREBRACKET, Symbols.RSQUAREBRACKET 66 | } 67 | 68 | Symbols.GTE = ">=" 69 | Symbols.LTE = "<=" 70 | Symbols.CMP_EQUALS = "==" 71 | Symbols.CMP_NEQUALS = "!=" 72 | Symbols.ASSIGN_PLUS = "+=" 73 | Symbols.ASSIGN_MINUS = "-=" 74 | Symbols.ASSIGN_MUL = "*=" 75 | Symbols.ASSIGN_DIV = "/=" 76 | Symbols.SINGLE_LINE_COMMENT_START = "//" 77 | Symbols.LOGICAL_AND = "&&" 78 | Symbols.LOGICAL_OR = "||" 79 | 80 | Symbols.TWO_CHARACTER_SYMBOLS = Flang.Util.Set{ 81 | Symbols.CMP_EQUALS, 82 | Symbols.CMP_NEQUALS, 83 | Symbols.LTE, 84 | Symbols.GTE, 85 | Symbols.ASSIGN_PLUS, 86 | Symbols.ASSIGN_MINUS, 87 | Symbols.ASSIGN_MUL, 88 | Symbols.ASSIGN_DIV, 89 | Symbols.LOGICAL_AND, 90 | Symbols.LOGICAL_OR, 91 | Symbols.SINGLE_LINE_COMMENT_START 92 | } 93 | 94 | -- IDENTIFIER_STARTCHARS = string.letters 95 | -- IDENTIFIER_CHARS = string.letters + string.digits + "_" 96 | -- 97 | -- NUMBER_STARTCHARS = string.digits 98 | -- NUMBER_CHARS = string.digits + "." 99 | 100 | Symbols.IDENTIFIER_STARTCHARS = Flang.Util.Set{"a", "b", "c", "d", "e", "f", "g", "h", 101 | "i", "j", "k", "l", "m", "n", "o", "p", 102 | "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", 103 | "A", "B", "C", "D", "E", "F", "G", "H", "I", 104 | "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" 105 | } 106 | Symbols.IDENTIFIER_CHARS = Flang.Util.Set{"a", "b", "c", "d", "e", "f", "g", "h", 107 | "i", "j", "k", "l", "m", "n", "o", "p", 108 | "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", 109 | 110 | "A", "B", "C", "D", "E", "F", "G", "H", "I", 111 | "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", 112 | 113 | "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 114 | 115 | "_" 116 | } 117 | 118 | Symbols.NUMBER_STARTCHARS = Flang.Util.Set{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"} 119 | Symbols.NUMBER_CHARS = Flang.Util.Set{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "."} 120 | 121 | Symbols.STRING_STARTCHARS = Flang.Util.Set{"'", '"'} 122 | Symbols.NEWLINE = "\n" 123 | Symbols.WHITESPACE_CHARS = Flang.Util.Set{" ", "\t", Symbols.NEWLINE} 124 | 125 | ----------------------------------------------------------------------- 126 | -- TokenTypes for things other than symbols and keywords 127 | ----------------------------------------------------------------------- 128 | Symbols.STRING = "String" 129 | Symbols.IDENTIFIER = "Identifier" 130 | Symbols.NUMBER = "Number" 131 | Symbols.WHITESPACE = "Whitespace" 132 | Symbols.COMMENT = "Comment" 133 | Symbols.EOF = "Eof" 134 | 135 | ----------------------------------------------------------------------- 136 | -- Special Interpreter Values 137 | ----------------------------------------------------------------------- 138 | 139 | -- Values returned just like regular execution values. But these are special! 140 | -- To ensure these values came from the Interpreter, we append some characters 141 | -- that are unsupported in the language ... probably 142 | Symbols.Control = {} 143 | Symbols.Control.BREAK = "break" .. " ~@#$`" 144 | Symbols.Control.CONTINUE = "continue" .. " ~@#$`" 145 | 146 | ----------------------------------------------------------------------- 147 | -- Set equality 148 | ----------------------------------------------------------------------- 149 | 150 | function Symbols.isKeyword(e) 151 | return Flang.Util.contains(Symbols.KEYWORDS, e) 152 | end 153 | 154 | function Symbols.isOneCharacterSymbol(e) 155 | return Flang.Util.contains(Symbols.ONE_CHARACTER_SYMBOLS, e) 156 | end 157 | 158 | function Symbols.isTwoCharacterSymbol(e) 159 | return Flang.Util.contains(Symbols.TWO_CHARACTER_SYMBOLS, e) 160 | end 161 | 162 | function Symbols.isWhitespace(e) 163 | return Flang.Util.contains(Symbols.WHITESPACE_CHARS, e) 164 | end 165 | 166 | function Symbols.isIdentifierStartChar(e) 167 | return Flang.Util.contains(Symbols.IDENTIFIER_STARTCHARS, e) 168 | end 169 | 170 | function Symbols.isIdentifierChar(e) 171 | return Flang.Util.contains(Symbols.IDENTIFIER_CHARS, e) 172 | end 173 | 174 | function Symbols.isNumberStartChar(e) 175 | return Flang.Util.contains(Symbols.NUMBER_STARTCHARS, e) 176 | end 177 | 178 | function Symbols.isNumberChar(e) 179 | return Flang.Util.contains(Symbols.NUMBER_CHARS, e) 180 | end 181 | 182 | function Symbols.isStringStartChar(e) 183 | return Flang.Util.contains(Symbols.STRING_STARTCHARS, e) 184 | end 185 | -------------------------------------------------------------------------------- /lang/base/token.lua: -------------------------------------------------------------------------------- 1 | Token = {} 2 | Flang.Token = Token 3 | Token.__index = Token 4 | 5 | --[[ 6 | Takes a character as input 7 | ]] 8 | function Token:new(startCharacter) 9 | if not startCharacter then 10 | error("nil constructor!") 11 | end 12 | 13 | -- The cargo is a character object. The position info is encoded inside it already! 14 | o = { 15 | cargo = startCharacter.cargo, 16 | lineIndex = startCharacter.lineIndex, 17 | columnIndex = startCharacter.columnIndex, 18 | 19 | -- The token type is known after consuming characters, thus is starts as nil 20 | type = nil 21 | } 22 | 23 | setmetatable(o, self) 24 | self.__index = self 25 | return o 26 | end 27 | 28 | --[[ 29 | Takes a token as input 30 | ]] 31 | function Token:copy(token) 32 | if not token then 33 | error("nil constructor!") 34 | end 35 | 36 | o = { 37 | cargo = token.cargo, 38 | lineIndex = token.lineIndex, 39 | columnIndex = token.columnIndex, 40 | type = token.type 41 | } 42 | 43 | setmetatable(o, self) 44 | self.__index = self 45 | return o 46 | end 47 | 48 | --[[ 49 | Say some nice, informative words about how they messed up 50 | ]] 51 | function Token:abort(msg) 52 | error(msg .. "\n\n" .. tostring(self)) 53 | end 54 | 55 | function Token:__tostring() 56 | cargo = self.cargo 57 | if cargo == " " then cargo = "\tSPACE" end 58 | if cargo == "\n" then cargo = "\tNEWLINE" end 59 | if cargo == "\t" then cargo = "\tTAB" end 60 | if cargo == Character.ENDMARK then cargo = "\tEOF" end 61 | 62 | tabber = "\t" 63 | -- Add an extra tab so it looks nice 64 | if (self.type == nil or string.len(self.type) < 7) then 65 | tabber = tabber .. "\t" 66 | end 67 | 68 | return 69 | "{line: '" .. self.lineIndex .. "'" 70 | .. "\t column: '" .. self.columnIndex .. "'" 71 | .. "\t type '" .. (self.type or "") .. "'" 72 | .. tabber .. "'" .. cargo .. "'}" 73 | end 74 | -------------------------------------------------------------------------------- /lang/base/util.lua: -------------------------------------------------------------------------------- 1 | Util = {} 2 | Flang.Util = Util 3 | Util.__index = Util 4 | 5 | Flang.DEBUG_LOGGING = false 6 | Flang.VERBOSE_LOGGING = false 7 | 8 | --[[ 9 | Wraps a string or object tostring inside single quotes 10 | ]] 11 | function dq(s) 12 | return "{'" .. tostring(s) .. "'}" 13 | end 14 | 15 | -- turn the Table of {element, ...} into a table of {element = true, ...} 16 | -- will be queried later 17 | function Util.Set(table) 18 | local s = {} 19 | for _,v in pairs(table) do s[v] = true end 20 | return s 21 | end 22 | 23 | -- Returns true if element in set, nil otherwise 24 | function Util.contains(set, element) 25 | return set[element] 26 | end 27 | 28 | --[[ 29 | Creates strings like: 30 | {a:1, b:2, c:4} 31 | ]] 32 | function Util.set_to_string(table, dont_print_key) 33 | local result = "{" 34 | local add_comma = false 35 | 36 | for k,v in pairs(table) do 37 | if add_comma then 38 | result = result .. ", " 39 | else 40 | add_comma = true 41 | end 42 | 43 | if (dont_print_key) then 44 | result = result 45 | else 46 | result = result .. tostring(k) .. ":" 47 | end 48 | if (Util.isTable(v) and v.token) then 49 | result = result .. tostring(v.token.cargo) 50 | else 51 | result = result .. tostring(v) 52 | end 53 | end 54 | -- result = result .. "}" 55 | return result .. "}" 56 | end 57 | 58 | function Util.set_to_string_dumb(table) 59 | local result = "{" 60 | local add_comma = false 61 | 62 | for k,v in pairs(table) do 63 | if add_comma then 64 | result = result .. ", " 65 | else 66 | add_comma = true 67 | end 68 | 69 | result = result .. tostring(k) .. ":" .. tostring(v) 70 | end 71 | return result .. "}" 72 | end 73 | 74 | function Util.set_to_string_nested_nodes(table, dont_print_key) 75 | local result = "{" 76 | local add_comma = false 77 | 78 | for k,v in pairs(table) do 79 | if add_comma then 80 | result = result .. ", " 81 | else 82 | add_comma = true 83 | end 84 | 85 | -- v is a node 86 | result = result .. tostring(v.value) 87 | end 88 | return result .. "}" 89 | end 90 | 91 | function Util.concat_table_to_string(tbl) 92 | local parts = {} 93 | for _,v in pairs(tbl) do 94 | if (Util.isTable(v)) then 95 | parts[#parts + 1] = Util.set_to_string_dumb(v) 96 | else 97 | parts[#parts + 1] = tostring(v) 98 | end 99 | end 100 | 101 | return table.concat(parts, " ") 102 | end 103 | 104 | function Util.isNumber(val) 105 | return type(val) == "number" 106 | end 107 | 108 | function Util.isTable(val) 109 | return type(val) == "table" 110 | end 111 | 112 | function Util.isString(val) 113 | return type(val) == "string" 114 | end 115 | 116 | -- Returns line numbers in the source 117 | function Util.getLineNumbers(source, lineStart, linesToPrint) 118 | local msg = "\n" 119 | 120 | local lineNum = 0 121 | local lastLineToPrint = lineStart + linesToPrint 122 | for line in source:gmatch("([^\n]*)\n?") do 123 | if (lineNum > lastLineToPrint) then 124 | break 125 | end 126 | 127 | if (lineNum >= lineStart) then 128 | msg = msg .. "\n" .. lineNum .. "> " .. line 129 | end 130 | lineNum = lineNum + 1 131 | end 132 | 133 | return msg 134 | end 135 | -------------------------------------------------------------------------------- /lang/samples/1.flang: -------------------------------------------------------------------------------- 1 | alpha = 15 2 | resultValue = "delta" 3 | b = alpha / resultValue 4 | 5 | if (x <= 4) { 6 | x = 0 7 | } 8 | 9 | pi = 3.141592 10 | -------------------------------------------------------------------------------- /lang/samples/2.flang: -------------------------------------------------------------------------------- 1 | alpha = 15 2 | resultValue = "delta" 3 | b = alpha / resultValue 4 | 5 | if (x <= 4) { 6 | x = 0 7 | } 8 | 9 | pi = 3.141592 10 | 11 | b = true 12 | y = 1 + 2*3 / 4 - (10/2) 13 | someBoolean = false 14 | 15 | if (b == true && y < 15) { 16 | someBoolean = true 17 | } 18 | -------------------------------------------------------------------------------- /lang/samples/3.flang: -------------------------------------------------------------------------------- 1 | ((((((((((((1 + 3)))))))))))) * 4 2 | -------------------------------------------------------------------------------- /lang/samples/4.flang: -------------------------------------------------------------------------------- 1 | 7 + 3 * (10 / (12 / (3 + 1) - 1)) / (2 + 3) - 5 - 3 + (8) 2 | -------------------------------------------------------------------------------- /lang/samples/array1.flang: -------------------------------------------------------------------------------- 1 | emptyArray = [] 2 | 3 | arr = [1, 2, 3] 4 | arrLength = arr.length() 5 | 6 | for (i=0; arr.length()) { 7 | v = arr[i] 8 | // Core.luaPrint(v) 9 | } 10 | 11 | arr[0] = 2 12 | 13 | // for (i in arr) { 14 | // Core.luaPrint(i) 15 | // } 16 | -------------------------------------------------------------------------------- /lang/samples/array2.flang: -------------------------------------------------------------------------------- 1 | arr = [1, 2, 3] 2 | v = arr["f"] 3 | -------------------------------------------------------------------------------- /lang/samples/array3.flang: -------------------------------------------------------------------------------- 1 | arr = [1, 2, 3, 4, 5] 2 | arr[0] = 20000 3 | arr[1] += 20000 4 | arr[2] -= 20000 5 | arr[3] *= 20000 6 | arr[4] /= 20000 7 | 8 | barr = [2] 9 | barr[5 - 5] = 0 10 | 11 | star = [4, 23] 12 | star[1] = "fish" 13 | 14 | x = star[0] 15 | y = star[1] 16 | z = x + 4 17 | -------------------------------------------------------------------------------- /lang/samples/array4.flang: -------------------------------------------------------------------------------- 1 | star = ["fish", 1] 2 | star[0] = 4 3 | y = star[0] 4 | 5 | x = star[1] 6 | z = x + 4 7 | -------------------------------------------------------------------------------- /lang/samples/b1.flang: -------------------------------------------------------------------------------- 1 | d = true 2 | b = false 3 | 4 | truey = 34 5 | -------------------------------------------------------------------------------- /lang/samples/broken_string.flang: -------------------------------------------------------------------------------- 1 | resultValue = "delta 2 | alpha = 15 3 | -------------------------------------------------------------------------------- /lang/samples/comment1.flang: -------------------------------------------------------------------------------- 1 | if (true) { 2 | x = 4 3 | //y=1 4 | a = 0 5 | // this is a comment! 6 | x=20 // niceee 7 | } 8 | 9 | // eof or end of function, you decide 10 | -------------------------------------------------------------------------------- /lang/samples/complete.flang: -------------------------------------------------------------------------------- 1 | alpha15 = 15 2 | boolTrue = true 3 | 4 | bFive = alpha15 / 3 5 | 6 | pi = 3.141592 7 | 8 | aFalse = 3 > 4 9 | bTrue = 1 < 2 10 | cTrue = 5 >= 5 11 | dTrue = 6 <= 6 12 | 13 | eTrue = 2 *3 > 4 - 2 14 | 15 | shouldBeFalse=!true 16 | 17 | under_score_var = 12 18 | false2 = !( 3>+4) 19 | 20 | modulus_3 = 18 % 5 21 | modulus_2 = -1 % 3 22 | 23 | ifShouldBe6 = 0 24 | if (3 < 1) { 25 | ifShouldBe6 = 2 26 | } elseif (1 > 6) { 27 | 28 | } elseif (4 == 4) { 29 | ifShouldBe6 = 6 30 | } 31 | 32 | ifShouldBe7 = 0 33 | ifShouldBe10 = 0 34 | if (true) { 35 | if (true) { 36 | if (false) { 37 | 38 | } else { 39 | ifShouldBe10 = 10 40 | ifShouldBe7 = 7 41 | } 42 | } 43 | } 44 | 45 | forShouldBe100 = 0 46 | for (i=0; i < 100; i += 1) { 47 | forShouldBe100 = forShouldBe100 + 1 48 | } 49 | 50 | asShouldBe11 = 10 51 | asShouldBe11+=1 52 | 53 | asShouldBe8=10 54 | asShouldBe8-=2 55 | 56 | asShouldBe12=4 57 | asShouldBe12*=3 58 | 59 | asShouldBe2p5=5 60 | asShouldBe2p5/=2 61 | 62 | eForShouldBe10000 = 0 63 | for (i=0; 10000) { 64 | eForShouldBe10000+=1 65 | } 66 | 67 | eForShouldBe5000 = 0 68 | for (i=0; 10000; 2) { 69 | eForShouldBe5000+=1 70 | } 71 | 72 | // sooooo coooooool 73 | 74 | def add(addArgx, addArgy) { 75 | return addArgx + addArgy 76 | } 77 | 78 | def mul(mulArgx, mulArgy) { 79 | return mulArgx * mulArgy 80 | } 81 | 82 | methodShouldBe5 = add(6, -1) 83 | methodShouldBe18 = mul(3, 6) 84 | methodShouldBe100 = mul(5, add(7, 13)) 85 | 86 | stringShouldBeHelloWorld = "Hello World!" 87 | 88 | table = [1, 2, 3, 4, 5] 89 | table[0] = 20 90 | table[1] += 20 91 | table[2] -= 20 92 | table[3] *= 20 93 | table[4] /= 20 94 | 95 | barr = [2] 96 | barr[5 - 5] = 0 97 | 98 | star = [4, 23] 99 | star[1] = "fish" 100 | 101 | arrayShouldBe20 = table[0] 102 | arrayShouldBe22 = table[1] 103 | arrayShouldBe24 = arrayShouldBe20 + 4 104 | arrayShouldBeNeg17 = table[2] 105 | arrayShouldBe80 = table[3] 106 | arrayShouldBeOhh25 = table[4] 107 | 108 | strTable = ["fish", 1] 109 | strTable["cat"] = "moon" 110 | arrayShouldBeMoon = strTable["cat"] 111 | 112 | sumTable = [1, 2, 3, 4, 5] 113 | arrayIteratorShouldBe15 = 0 114 | for (i in sumTable) { 115 | arrayIteratorShouldBe15 += i 116 | } 117 | 118 | r = [1,2,3] 119 | kvpArrayIteratorShouldBe9 = 0 120 | for (k,v in r) { 121 | kvpArrayIteratorShouldBe9 += k + v 122 | } 123 | -------------------------------------------------------------------------------- /lang/samples/conditional_chaining1.flang: -------------------------------------------------------------------------------- 1 | if (true && false) { 2 | Core.luaPrint("hello") 3 | } 4 | 5 | if (true || false) { 6 | Core.luaPrint("world") 7 | } 8 | 9 | if (true && (true || false)) { 10 | Core.luaPrint("cool") 11 | } 12 | 13 | if (true && false || true) { 14 | Core.luaPrint("uhh") 15 | } 16 | 17 | if (true || false && true) { 18 | Core.luaPrint("nice") 19 | } 20 | -------------------------------------------------------------------------------- /lang/samples/efor1.flang: -------------------------------------------------------------------------------- 1 | v = 0 2 | 3 | for (i=0; 10000) { 4 | v+=1 5 | } 6 | -------------------------------------------------------------------------------- /lang/samples/efor2.flang: -------------------------------------------------------------------------------- 1 | x = 0 2 | y=0 3 | v=0 4 | 5 | for (i=5; i<10; i+=1) { 6 | v+=1 7 | } 8 | 9 | for (i=1; 10) { 10 | x+=1 11 | } 12 | 13 | for (i=1; 10; 2) { 14 | y+=1 15 | } 16 | -------------------------------------------------------------------------------- /lang/samples/eq1.flang: -------------------------------------------------------------------------------- 1 | a = 3 > 4 2 | b = 1 < 2 3 | c = 5 >= 5 4 | d = 6 <= 6 5 | 6 | e = 2*3 > 4 - 2 7 | 8 | equals = 4 == 4 9 | nequals = 4 != 4 10 | 11 | test = !a 12 | -------------------------------------------------------------------------------- /lang/samples/eq2.flang: -------------------------------------------------------------------------------- 1 | a = !false 2 | b = !( 3>+4) 3 | -------------------------------------------------------------------------------- /lang/samples/flow_control1.flang: -------------------------------------------------------------------------------- 1 | count = 0 2 | 3 | for (i=0 ; 10) { 4 | Core.luaPrint(i) 5 | if (i == 2) { 6 | if (true) { 7 | if (true) { 8 | break 9 | } 10 | } 11 | } 12 | count += 1 13 | } 14 | 15 | def helloThere() { 16 | if (true) { 17 | if (true) { 18 | if (true) { 19 | if (true) { 20 | if (true) { 21 | Core.luaPrint("hi") 22 | if (true) { 23 | return 1 24 | } 25 | } 26 | } 27 | } 28 | } 29 | } 30 | } 31 | 32 | y=4 33 | x = helloThere() 34 | -------------------------------------------------------------------------------- /lang/samples/flow_control2.flang: -------------------------------------------------------------------------------- 1 | for (i=0 ; 10) { 2 | break 3 | } 4 | 5 | def helloThere() { 6 | return 1 7 | } 8 | 9 | x = helloThere() 10 | -------------------------------------------------------------------------------- /lang/samples/flow_control3.flang: -------------------------------------------------------------------------------- 1 | for (i=0 ; 10) { 2 | if (i == 3){ 3 | break 4 | } 5 | Core.luaPrint(i) 6 | } 7 | 8 | Core.luaPrint("standard for") 9 | 10 | for (i=0; i<10; i+=1) { 11 | if (i == 3){ 12 | break 13 | } 14 | Core.luaPrint(i) 15 | } 16 | 17 | Core.luaPrint("collection iteration") 18 | 19 | arr = [0,1,2,3,4,5,6,7,8,9] 20 | for (i in arr) { 21 | if (i == 3){ 22 | break 23 | } 24 | Core.luaPrint(i) 25 | } 26 | -------------------------------------------------------------------------------- /lang/samples/for1.flang: -------------------------------------------------------------------------------- 1 | x = 0 2 | y = 100000 3 | for (i=0; i < y; i = i + 1) { 4 | x = x + 1 5 | k = 10 6 | } 7 | -------------------------------------------------------------------------------- /lang/samples/for_array1.flang: -------------------------------------------------------------------------------- 1 | arr = [1, 2, 3] 2 | 3 | arr["fish"] = 3434 4 | arr[10000] = 9 5 | 6 | for (i in arr) { 7 | Core.luaPrint(i) 8 | } 9 | 10 | arrayIteratorShouldBe15 = 0 11 | for (i in [1, 2, 3, 4, 5]) { 12 | arrayIteratorShouldBe15 += i 13 | } 14 | -------------------------------------------------------------------------------- /lang/samples/func1.flang: -------------------------------------------------------------------------------- 1 | var1 = 17 * 8 2 | 3 | def thing(x, y) { 4 | return x + y 5 | 6 | // This line should not be executed 7 | a = 14 8 | } 9 | 10 | z = thing(1, 2) 11 | sum = 4 + 5 12 | -------------------------------------------------------------------------------- /lang/samples/func2.flang: -------------------------------------------------------------------------------- 1 | def stuff(x, y, zed) { 2 | // void methods 3 | a = 5 + 3 4 | } 5 | 6 | // invocations as statements 7 | stuff(1, 2, 3001) 8 | -------------------------------------------------------------------------------- /lang/samples/func3.flang: -------------------------------------------------------------------------------- 1 | var1 = 17 * 8 2 | 3 | def thing(x, y) { 4 | return x + y 5 | } 6 | 7 | z = thing(1, 2) 8 | sum = 4 + 5 9 | 10 | x = Foo.add(1, 2) 11 | y = Bar.sub(1,3).pow(4).zero() 12 | -------------------------------------------------------------------------------- /lang/samples/funcCall1.flang: -------------------------------------------------------------------------------- 1 | x = Core.helloWorld(1) 2 | 3 | y = 3 4 | -------------------------------------------------------------------------------- /lang/samples/funcCall2.flang: -------------------------------------------------------------------------------- 1 | for(i = 0 ; 10000) { 2 | x = Core.helloWorld(1) 3 | } 4 | 5 | y = 3 6 | -------------------------------------------------------------------------------- /lang/samples/hard_math.flang: -------------------------------------------------------------------------------- 1 | x = 1 + 3 * 7 2 | y = 11 / (3 + 8) 3 | -------------------------------------------------------------------------------- /lang/samples/if1.flang: -------------------------------------------------------------------------------- 1 | if (5 < 4) { 2 | x = 4 3 | } elseif (false) { 4 | k = 1 5 | } else { 6 | finished = true 7 | } 8 | 9 | if (false) { 10 | y = 10 11 | } 12 | 13 | if (3 < 1) { 14 | three = 3 15 | } elseif (4 == 4) { 16 | six = 5 17 | } 18 | 19 | boolTrue = true 20 | if (boolTrue) { 21 | five = 5 22 | } else { 23 | two = 2 24 | } 25 | -------------------------------------------------------------------------------- /lang/samples/if2.flang: -------------------------------------------------------------------------------- 1 | if (true) { 2 | x = 4 3 | y=1 4 | } 5 | -------------------------------------------------------------------------------- /lang/samples/iter_for1.flang: -------------------------------------------------------------------------------- 1 | tbl = [] 2 | tbl[0] = "fish" 3 | tbl["a"] = 1 4 | tbl["b"] = 2 5 | tbl["c"] = 3 6 | 7 | for (k,v in tbl) { 8 | Core.luaPrint(k, v) 9 | } 10 | 11 | for (v in tbl) { 12 | Core.luaPrint("yeet", v) 13 | } 14 | 15 | r = [1,2,3] 16 | for (k,v in r) { 17 | Core.luaPrint(k, v) 18 | } 19 | -------------------------------------------------------------------------------- /lang/samples/iter_for2.flang: -------------------------------------------------------------------------------- 1 | r = [1,2,3] 2 | for (k,v in r) { 3 | Core.luaPrint(k, v) 4 | } 5 | -------------------------------------------------------------------------------- /lang/samples/repeat_execution_test.flang: -------------------------------------------------------------------------------- 1 | arr = [] 2 | Core.luaPrint(arr) 3 | arr[1] = "fish" 4 | Core.luaPrint(arr) 5 | -------------------------------------------------------------------------------- /lang/samples/s1.flang: -------------------------------------------------------------------------------- 1 | alpha = 15 2 | b = 1 + 3 3 | 4 | c = alpha + b 5 | -------------------------------------------------------------------------------- /lang/samples/s2.flang: -------------------------------------------------------------------------------- 1 | a = "nice" 2 | 3 | b = 1 4 | -------------------------------------------------------------------------------- /lang/samples/scope1.flang: -------------------------------------------------------------------------------- 1 | globalA = 1 2 | globalB = 10 3 | globalShouldBe20 = 0 4 | 5 | if (1 == 1) { 6 | x = 3 7 | if (1 == 1) { 8 | y = 4 + globalB 9 | globalShouldBe20 = x + y + 3 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lang/samples/scope2.flang: -------------------------------------------------------------------------------- 1 | globalA = 1 2 | globalB = 10 3 | globalShouldBe20 = 0 4 | 5 | def add(x, y) { 6 | return x + y 7 | } 8 | 9 | if (globalA > 0) { 10 | x = 2 11 | if (1 == 1) { 12 | y = x + 1 13 | globalB = y + 4 14 | 15 | if (add(x, y) == add(y, x)) { 16 | globalShouldBe20 = 20 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lang/samples/scope3.flang: -------------------------------------------------------------------------------- 1 | def add(x, y) { 2 | return x + y 3 | } 4 | 5 | globalA = add(1, 2) 6 | -------------------------------------------------------------------------------- /lang/samples/scope4.flang: -------------------------------------------------------------------------------- 1 | def add(x_, y_) { 2 | return x_ + y_ 3 | } 4 | 5 | globalShouldBe20 = 0 6 | x = 1 7 | y = 2 8 | if (add(x, y) == add(y, x)) { 9 | globalShouldBe20 = 20 10 | } 11 | -------------------------------------------------------------------------------- /lang/samples/string_methods1.flang: -------------------------------------------------------------------------------- 1 | s = "fish" 2 | Core.luaPrint(s.sub(1, 2)) 3 | Core.luaPrint(s.sub(3)) 4 | Core.luaPrint("fish".sub(3)) 5 | -------------------------------------------------------------------------------- /lang/samples/string_methods2.flang: -------------------------------------------------------------------------------- 1 | x = string.sub("fish", 3) 2 | 3 | y = 3 4 | -------------------------------------------------------------------------------- /lang/samples/wacky.flang: -------------------------------------------------------------------------------- 1 | phrase = "hi there" 2 | chars = phrase.getChars() 3 | -------------------------------------------------------------------------------- /locale/en/config.cfg: -------------------------------------------------------------------------------- 1 | [item-name] 2 | flang-chip=Flang chip 3 | invis-flang-chip=Invisible Flang Chip data 4 | 5 | [item-description] 6 | flang-chip=The Flang chip! 7 | invis-flang-chip=Flang chip data 8 | 9 | [entity-name] 10 | flang-chip=Flang chip 11 | invis-flang-chip=Invisible Flang chip data 12 | 13 | [entity-description] 14 | flang-chip=The Flang chip!!! 15 | invis-flang-chip=Flang chip data 16 | 17 | [controls] 18 | flang-open-editor=Open Flang Editor on Selection 19 | -------------------------------------------------------------------------------- /prototypes/custominput.lua: -------------------------------------------------------------------------------- 1 | data:extend({ 2 | { 3 | type = "custom-input", 4 | name = "flang-open-editor", 5 | key_sequence = "SHIFT + F", 6 | consuming = "script-only" 7 | 8 | -- 'consuming' 9 | -- available options: 10 | -- none: default if not defined 11 | -- all: if this is the first input to get this key sequence then no other inputs listening for this sequence are fired 12 | -- script-only: if this is the first *custom* input to get this key sequence then no other *custom* inputs listening for this sequence are fired. Normal game inputs will still be fired even if they match this sequence. 13 | -- game-only: The opposite of script-only: blocks game inputs using the same key sequence but lets other custom inputs using the same key sequence fire. 14 | }}) 15 | -------------------------------------------------------------------------------- /prototypes/entity.lua: -------------------------------------------------------------------------------- 1 | -- see \Factorio\data\base\prototypes\entity\entities.lua line 10459 2 | local flangChipEntity = table.deepcopy(data.raw["constant-combinator"]["constant-combinator"]) 3 | flangChipEntity.name = "flang-chip" 4 | flangChipEntity.minable = {hardness = 0.2, mining_time = 0.5, result = "flang-chip"} 5 | flangChipEntity.max_health = 271 6 | flangChipEntity.icon = "__Flang__/graphics/flangchip.png" 7 | flangChipEntity.item_slot_count = 100 8 | 9 | local invisChipEntity = table.deepcopy(data.raw["programmable-speaker"]["programmable-speaker"]) 10 | invisChipEntity.name = "invis-flang-chip" 11 | invisChipEntity.minable = {hardness = 0, mining_time = 0, result = "invis-flang-chip"} 12 | invisChipEntity.max_health = 3 13 | invisChipEntity.icon = "__Flang__/graphics/flangchip.png" 14 | invisChipEntity.item_slot_count = 100 15 | invisChipEntity.selectable_in_game = false 16 | invisChipEntity.collision_mask = {"not-colliding-with-itself"} 17 | invisChipEntity.flags = {"player-creation", "not-repairable"} 18 | invisChipEntity.sprite = 19 | { 20 | layers = 21 | { 22 | { 23 | filename = "__Flang__/graphics/empty.png", 24 | priority = "extra-high", 25 | width = 30, 26 | height = 89, 27 | shift = util.by_pixel(-2, -39.5), 28 | hr_version = 29 | { 30 | filename = "__Flang__/graphics/empty.png", 31 | priority = "extra-high", 32 | width = 59, 33 | height = 178, 34 | shift = util.by_pixel(-2.25, -39.5), 35 | scale = 0.5 36 | } 37 | }, 38 | { 39 | filename = "__Flang__/graphics/empty.png", 40 | priority = "extra-high", 41 | width = 119, 42 | height = 25, 43 | shift = util.by_pixel(52.5, -2.5), 44 | draw_as_shadow = true, 45 | hr_version = 46 | { 47 | filename = "__Flang__/graphics/empty.png", 48 | priority = "extra-high", 49 | width = 237, 50 | height = 50, 51 | shift = util.by_pixel(52.75, -3), 52 | draw_as_shadow = true, 53 | scale = 0.5 54 | } 55 | } 56 | } 57 | } 58 | -- https://wiki.factorio.com/Types/Energy 59 | invisChipEntity.energy_usage = "150kW" 60 | -- https://wiki.factorio.com/Types/ElectricUsagePriority 61 | invisChipEntity.energy_source = { 62 | type = "electric", 63 | usage_priority = "primary-input", 64 | emissions = 0 65 | } 66 | 67 | data:extend({flangChipEntity, invisChipEntity}) 68 | -------------------------------------------------------------------------------- /prototypes/item.lua: -------------------------------------------------------------------------------- 1 | local flangChip = {} 2 | 3 | flangChip.name = "flang-chip" 4 | flangChip.type = "item" 5 | flangChip.icon = "__Flang__/graphics/flangchip.png" 6 | flangChip.flags = { "goes-to-quickbar" } 7 | flangChip.subgroup = "circuit-network" 8 | flangChip.place_result = "flang-chip" 9 | flangChip.stack_size = 50 10 | flangChip.icon_size = 32 11 | 12 | local invisChip = {} 13 | invisChip.name = "invis-flang-chip" 14 | invisChip.type = "item" 15 | invisChip.icon = "__Flang__/graphics/flangchip.png" 16 | invisChip.flags = { "hidden" } 17 | invisChip.subgroup = "circuit-network" 18 | invisChip.place_result = "invis-flang-chip" 19 | invisChip.order = "b[combinators]-c[invis-flang-chip]" 20 | invisChip.stack_size = 16384 21 | invisChip.icon_size = 32 22 | 23 | data:extend({flangChip, invisChip}) 24 | -------------------------------------------------------------------------------- /prototypes/recipe.lua: -------------------------------------------------------------------------------- 1 | local recipe = {} 2 | recipe.type = "recipe" 3 | recipe.name = "flang-chip" 4 | recipe.enabled = true 5 | recipe.ingredients = {{"copper-plate",200},{"steel-plate",50}} 6 | recipe.result = "flang-chip" 7 | 8 | data:extend{recipe} 9 | -------------------------------------------------------------------------------- /prototypes/sprite.lua: -------------------------------------------------------------------------------- 1 | data:extend({ 2 | { 3 | type = "sprite", 4 | name = "close", 5 | filename = "__Flang__/graphics/close.png", 6 | priority = "extra-high-no-scale", 7 | width = 64, 8 | height = 64, 9 | scale = 1, 10 | }, 11 | { 12 | type = "sprite", 13 | name = "play", 14 | filename = "__Flang__/graphics/play.png", 15 | priority = "extra-high-no-scale", 16 | width = 64, 17 | height = 64, 18 | scale = 1, 19 | }, 20 | { 21 | type = "sprite", 22 | name = "stop", 23 | filename = "__Flang__/graphics/stop.png", 24 | priority = "extra-high-no-scale", 25 | width = 64, 26 | height = 64, 27 | scale = 1, 28 | } 29 | }) 30 | -------------------------------------------------------------------------------- /prototypes/style.lua: -------------------------------------------------------------------------------- 1 | local WIDTH = 800 2 | 3 | -- All other widths reference this value 4 | local EDITOR_WIDTH = WIDTH 5 | local EDITOR_HEIGHT = 600 6 | data.raw["gui-style"].default.flang_editor_window_style = { 7 | type = "textbox_style", 8 | minimal_height = EDITOR_HEIGHT, 9 | minimal_width = EDITOR_WIDTH, 10 | maximal_height = EDITOR_HEIGHT, 11 | maximal_width = EDITOR_WIDTH, 12 | want_ellipsis = false 13 | } 14 | 15 | local INFO_WINDOW_WIDTH = WIDTH 16 | local INFO_WINDOW_HEIGHT = 350 17 | data.raw["gui-style"].default.flang_info_window_style = { 18 | type = "textbox_style", 19 | minimal_height = INFO_WINDOW_HEIGHT, 20 | minimal_width = INFO_WINDOW_WIDTH, 21 | maximal_height = INFO_WINDOW_HEIGHT, 22 | maximal_width = INFO_WINDOW_WIDTH, 23 | want_ellipsis = false, 24 | single_line = false, 25 | } 26 | 27 | local MENU_WINDOW_WIDTH = WIDTH 28 | local MENU_WINDOW_HEIGHT = 300 29 | data.raw["gui-style"].default.flang_menu_window_style = { 30 | type = "textbox_style", 31 | minimal_height = MENU_WINDOW_HEIGHT, 32 | minimal_width = MENU_WINDOW_WIDTH, 33 | maximal_height = MENU_WINDOW_HEIGHT, 34 | maximal_width = MENU_WINDOW_WIDTH, 35 | want_ellipsis = false, 36 | } 37 | -------------------------------------------------------------------------------- /save-commands.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands": [ 3 | "** : python builder.py" 4 | ] 5 | } 6 | --------------------------------------------------------------------------------