This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
SquidDev's profile picture

PPI - Pre-Processor Includes

Started by SquidDev, 30 May 2014 - 03:36 PM
SquidDev #1
Posted 30 May 2014 - 05:36 PM
Ok. I got tired of having one massive lua file for each program, or having many modules and having to copy and paste them each time just to paste them. I wrote a little utility that takes a file, reads its dependencies and adds them to one file.

How to use:
Combiner.lua <Initial File> <Output File> [-l]
Adding the -l flag means that named includes will be defined in as locals, and so do not exist for the entire session. Use it for programs, leave it out for APIs.

Syntax
Spoiler

–@require whatIWantToRequire.lua Requires whatIWantToRequire.lua in your compiled file. (you can also use –@import)
–@name RandomName. Required files will be referred to as RandomName.method() instead of method().
@include AFile.lua The file will be parsed and then included straight away, inside the source of the other file. If code is both imported and included then the code will be duplicated.


Example
Spoiler

TopFIle.lua

--@require Debug.lua
--@require GUI.lua
Debug.Log("Hai")
Button("Click me")

Debug.lua

--@name Debug
local File = fs.open("thing", "w")
function Log(Message) do
  File.writeLine(Message)
  File.flush()
end

GUI.lua

function Button(Text) do
  error("Who needs buttons?")
end

To 'compile': Combiner.lua TopFile.lua Entire.lua

Each file still runs in its own scope, but can still depend on other files.

Get it from Pastebin: pastebin get MyLaEV82 Combiner.lua
Edited on 06 June 2014 - 09:44 AM
CometWolf #2
Posted 30 May 2014 - 05:42 PM
Wouldn't this essentially be… the same as os.loadAPI? Well except being local from what i can tell.
Edited on 30 May 2014 - 03:43 PM
SquidDev #3
Posted 30 May 2014 - 05:51 PM
Wouldn't this essentially be… the same as os.loadAPI? Well except being local from what i can tell.
That is exactly what it is, just at 'compile' time. I wrote this so the user wouldn't have to download multiple files but I can still write in a reasonably modular format.
apemanzilla #4
Posted 30 May 2014 - 06:39 PM
So this compiles your APIs and program into one usable program?
SquidDev #5
Posted 30 May 2014 - 07:00 PM
So this compiles your APIs and program into one usable program?
Theoretically usable. I think it should work perfectly but someone will break it I'm sure :)/> My code is never perfect.
Edited on 30 May 2014 - 05:00 PM
skwerlman #6
Posted 31 May 2014 - 05:20 AM
Any plans to allow conditional compiling? For example, if you have a file 'debug.lua' that requires 'output.lua', and a file 'button.lua' that also requires 'output.lua', and you're compiling 'menu.lua', which needs both 'debug.lua' and 'button.lua', you don't want to load 'output.lua' twice.
In C++, there's a PPD, '#IFNDEF', that checks if a variable has been '#DEFINE'd. This can be used to see if a file was already loaded. For simplicity's sake, I'd implement it here as something like '–@isnotloaded', and have the compiler keep track of which files were loaded, instead of having an analog for '#DEFINE'.

EDIT: Looks like you forgot a check to see if it's being used in 1.6 or not. fs.getDir is a 1.6 feature, so this won't work on earlier versions.
Edited on 31 May 2014 - 03:25 AM
oeed #7
Posted 31 May 2014 - 06:22 AM
This is actually I really cool idea. I can see this growing to something even better soon.
theoriginalbit #8
Posted 31 May 2014 - 07:22 AM
how does it go handling recursive includes?

a.lua

--@require b.lua

b.lua

--@require a.lua
SquidDev #9
Posted 31 May 2014 - 10:26 AM
how does it go handling recursive includes?

a.lua

--@require b.lua

b.lua

--@require a.lua

It can handle nested includes but not recursive ones. It works from 'base up' - first include the files with no dependencies and then the ones that rely on those already included, and so on…

I'm not sure how it would work, it depends on how it uses those includes - if it references those includes inside functions then it should work fine, but if it references them in the main code then there would be errors:

a.lua

--@require b.lua
print(B)/>/>/>/>
a = "Hello"

b.lua

--@require a.lua
print(a)
b = "Hello"

The above wouldn't work at all but the following would:

a.lua

--@require b.lua
function work()
  print(B)/>/>
end
a = "Hello"

b.lua

--@require a.lua
function work()
  print(a)
end
b = "Hello"

I guess I could just detect this recursion and add them one after another but I can't stop the programmer being rather silly.
Edited on 31 May 2014 - 08:36 AM
oeed #10
Posted 31 May 2014 - 11:29 AM
–snip–

I haven't looked at your code, so I'm not really sure how it works, but you could probably have table of already included files and if it's on there then don't include it.
SquidDev #11
Posted 31 May 2014 - 11:53 AM
I haven't looked at your code, so I'm not really sure how it works, but you could probably have table of already included files and if it's on there then don't include it.

That is pretty much how I do it for loading, but not for printing them out. To summarize how it works:
  1. Read file, and check for dependencies
  2. For each dependency, read its dependencies and add them to the queue if they are not loaded already. It it has no dependencies then add it to the 'bottom' files
  3. For each element in the queue do step 2.
  4. For each 'bottom' file, include it in the output file
  5. Remove these bottom files from all files dependency lists. If files don't have any dependencies then include them next time.
  6. Go back to step 4 until there are no more files to be loaded.
With recursive includes there would be a deadlock at step 5 and neither file would be included, and so the program would just end without including either file. I guess at the end I could just go through any file not included and whack it in anyway.
SquidDev #12
Posted 06 June 2014 - 11:43 AM
I have issued a minor update which should mean that recursive includes are handled, This update also includes:
  • –@include command
  • Total re-write to optimise compilation time
  • More code reuse for named imports (this will however add some code at the top of your compiled file which you can remove if not using named imports).
  • -l flag for keeping code in the local namespace.
Creeper9207 #13
Posted 12 April 2016 - 02:40 AM
you really should close that file
Debug.lua

--@name Debug
local File = fs.open("thing", "w")
function Log(Message) do
  File.writeLine(Message)
  File.flush()
end
Anavrins #14
Posted 12 April 2016 - 03:23 AM
you really should close that file
Debug.lua

--@name Debug
local File = fs.open("thing", "w")
function Log(Message) do
  File.writeLine(Message)
  File.flush()
end
Nice two years gravedigging.
The .flush() function actually writes to file without closing it, so you can log something else without having the re-open it.
You would usually close the file handles right before the program exits.
Edited on 12 April 2016 - 01:23 AM
Creeper9207 #15
Posted 12 April 2016 - 04:19 AM
i had no idea this was two years old, really sorry about that, I have no idea how I actually got to this post