2016-08-22 4 views
3

В настоящее время я разрабатываю свой собственный язык программирования. Кодовая (в Lua) состоит из нескольких модулей, а именно:Lua Spaghetti Modules

  • первый, error.lua, не имеет зависимостей;
  • lexer.lua зависит только от ошибки.lua;
  • prototypes.lua также не имеет зависимости;
  • parser.lua, вместо этого, зависит от всех модулей выше;
  • interpreter.lua - это точка опоры всей кодовой базы. Это зависит от error.lua, parser.lua и memory.lua;
  • memory.lua зависит от functions.lua;
  • , наконец, functions.lua зависит от memory.lua и interpreter.lua. Это требуется изнутри memory.lua, поэтому мы можем сказать, что memory.lua также зависит от interpreter.lua.

С «А зависит от Б» Я имею в виду, что функции, объявленные в потребность, объявленных в B.

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

Чтобы дать конкретный пример моей проблемы, вот как interpreter.lua выглядит следующим образом:

--first, I require the modules that DON'T depend on interpreter.lua 
local parser, Error = table.unpack(require("parser")) 
--(since error.lua is needed both in the lexer, parser and interpreter module, 
--I only actually require it once in lexer.lua and then pass its result around) 

--Then, I should require memory.lua. But since memory.lua and 
--functions.lua need some functions from interpreter.lua to work, I just 
--forward declare the variables needed from those functions and then those functions themself: 

--forward declaration 
local globals, new_memory, my_nil, interpret_statement 

--functions I need to declare before requiring memory.lua 
local function interpret_block() 
    --uses interpret_statement and new_memory 
end 
local function interpret_expresion() 
    --uses new_memory, Error and my_nil 
end 

--Now I can safely require memory.lua: 
globals, new_memory, my_nil = require("memory.lua")(interpret_block, interpret_espression) 
--(I'll explain why it returns a function to call later) 

--Then I have to fulfill the forward declaration of interpret_executement: 
function interpret_executement() 
    --uses interpret_expression, new_memory and Error 
end 

--finally, the result is a function 
return function() 
    --uses parser, new_fuction and globals 
end 

Модуль memory.lua возвращает функцию, так что он может получить interpret_block и interpret_expression в качестве аргументов, как это:

--memory.lua 
return function(interpret_block, interpret_expression) 
    --declaration of globals, new_memory, my_nil 
    return globals, new_memory, my_nil 
end 

Теперь у меня появилась идея форвардных деклараций here и что из функций-как-модулей (например, в memory.lua, чтобы передать некоторые функции из модуля, требующего до требуемого модуля) here. Все они великие идеи, и я должен сказать, что они работают очень. Но вы платите в удобочитаемости.

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

Чувство, которое у меня есть, - это работа с кодом спагетти, только в большем масштабе.

Итак, как я могу решить проблему непонятного моего кода из-за того, что некоторые модули нуждаются друг в друге (что не требует, чтобы все переменные были глобальными, конечно)? Как программисты на других языках решают эту проблему? Как я должен реорганизовать свои модули? Существуют ли стандартные правила использования модулей Lua, которые также могут помочь мне в решении этой проблемы?

ответ

3

Если мы посмотрим на ваши файлы lua в виде ориентированного графика, где вершина указывает на зависимость от ее использования, целью является изменение вашего графика как дерева или леса, поскольку вы намерены избавиться от циклов ,

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

Вопрос в том, как избавиться от циклов?

Ответ выглядит следующим образом:

Рассмотрим узел N и рассмотрим {D1, D2, ..., Dm} своих прямых зависимостей. Если в этом наборе нет Di, которое напрямую или косвенно зависит от N, вы можете оставить N таким, какой он есть. В этом случае набор проблемных зависимостей выглядит следующим образом: {}

Однако, если у вас есть непустой набор, например: {PD1, ..., PDk}?

Затем вам необходимо проанализировать PDi для i между 1 и k вместе с N и посмотреть, что такое подмножество в каждом PDi, которое не зависит от N и какое подмножество N, которое не зависит от какого-либо PDi. Таким образом, вы можете определить N_base и N, PDi_base и PDi. N зависит от N_base, как и все элементы PDi, а PDi зависит от PDi_base и N_base.

Этот подход минимизирует круги в дереве зависимостей. Однако вполне возможно, что в этой группе существует набор функций {f1, ..., fl}, который не может быть перенесен в базу, как обсуждалось из-за зависимостей, и все еще существуют циклы. В этом случае вам нужно указать имя соответствующей группе, создать для нее модуль и перенести все функции в эту группу.

+0

Вау, я так и не ожидал ответа. В настоящее время проблема заключается в следующем: интерпретатор -> функции -> память <-> переводчик. Теперь я собираюсь проанализировать подмножества, как вы объяснили, и посмотреть, могу ли я что-то извлечь из этого. – user6245072

+0

Желаю вам удачи в этом, пожалуйста, дайте мне знать результат. –

+1

О, спасибо. Я остановился сосредоточиться только на модулях, и теперь я создаю дерево зависимостей функций. – user6245072