A Conditional Lua compiler!
Currently on version: 2.0.0
Now available through MPT! (currently on version 2.0.0-p42; usually updated within 15 minutes of main version)
What does it do?
Spoiler
CLua allows you to create modular CC Programs and release them as a single file, without needing to copy-paste them into a release file.Why'd you make it?
Spoiler
I felt inspired after seeing http://www.computerc...essor-includes/, so I decided to make a conditional compiler.How do I use it?
Spoiler
It works just like the preprocessor in C++. It scans for preprocessor directives (PPDs), and performs commands based on them.The current PPDs are:
#INCLUDE file FROM dir -- Copies the contents of the specified file to that point in the file. Use '~' as a shortcut to the library folder. Use '?here' as a shortcut to the current directory. Use '?there' as a shortcut to the output directory.
#DEFINE flag -- Sets flag at a given point. CLua keeps track of these flags to prevent infinite loops, so I recommend using something like #DEFINE <filename> at the start of each file.
#IFDEF flag ... #ENDIFDEF -- Only copies the code between #IFDEF and ENDIFDEF if flag has been set.
#IFNDEF flag ... #ENDIFNDEF -- Only copies the code between #IFNDEF and #ENDIFNDEF if flag has not been set.
#IFVAR variable ... #ENDIFVAR -- Only copies the code between #IFVAR and #ENDIFVAR if variable is defined.
#IFNVAR variable ... #ENDIFNVAR -- Only copies the code between #IFNVAR and #ENDIFNVAR if variable is not defined.
#SNIPPET -- Ignores all directives until the end of the file. Use at the start of a file if you want to be able to #INCLUDE it several times.
#EXEC code -- Executes arbitrary Lua code. Should not be used to set variables.
#LICENSE license [vars] -- Adds the specified license to the .licensing file for the current file.
#SETDYNAMICVAR variable:value ... -- adds a value the the list VAR.
#DYNAMIC-INCLUDE delim1 delim2 [text] ... -- Inserts a line into the current file, replacing text between delim1 and delim2 with its value in VAR.
To compile a program, use the following syntax:
clua <input> <output> [--help] [-?] [--log] [--version] [--dry-run] [--exec:<code> ...] [--dyn:<var>=<val>[;<var>=<val>...] ...] [--define:<flag>[;<flag>...] ...] [--quiet] [--silent] [--verbose] [--devel] [--no-debug] [--no-trim] [--self-update] [--to-path:<path>] [--from-path:<path>]
--help, -? - Display this help message and exit.
--log - Enables logging.
--version - Print version info and exit.
--dry-run - Runs through the compilation, but doesn't modify the output file.
--exec:<code> - Executes arbitrary code before compilation. Use ++ instead of spaces, or include the entire option in double quotes (").
--dyn:<var>=<val>[;<var>=<val>...] - Equivelent to #DYNAMIC-INCLUDE
--define:<flag>[;<flag>...] - Equivelent to #DEFINE.
--quiet - Do not print most messages.
--silent - Only print errors.
--verbose - Prints ALL messages.
--devel - Allows logging of DEVEL level messages. Useful only for debugging. Much slower than normal, especially for large programs. Not recommended.
--no-debug - Don't log DEBUG level messages.
--self-update - Downloads and runs the latest CLua installer, then reboots.
--no-trim - Prevents the trimming of empty lines when writing the output.
--to-path:/path/ - Specifies where to output the compiled program and license.
--from-path:/path/ - Specifies where to look for the main source file.
Don't worry about including clua's path when you call it; the installer takes care of that for you.When should I use it?
Spoiler
CLua really shines in large projects where managing code becomes a hassle, because multiple independent phases of a program get forced into a single massive file.It's also really useful when you use the same code in multiple projects; just write that code in a separate file, and #INCLUDE it.
When shouldn't I use it?
Spoiler
CLua is NOT a replacement for os.loadAPI(). Use PPI for that. (Don't worry, we're compatible!)CLua does not support more than one level of recursion. Loop detection would need way too much work to fix this, so it'll probably never change.
How do I install it?
Spoiler
Using pastebin
Simply type:pastebin run zPMasvZ2
to install it.If that doesn't work, type:
pastebin get zPMasvZ2 clua-inst
clua-inst
The installer will ask you where you'd like it installed; I recommend using /clua, but it's really a matter of preference.To update, just run:
clua --self-update
Using MPT
Simply runmpt ppa add skwerlman
mpt install clua
How do I uninstall it?
Spoiler
Step one: let me know why (assuming its an issue with the program itself). I'm always looking for feedback about the quality of programs, and knowing what people like or dislike is essential to creating a good program.Step two: Depends on how it was installed.
If you installed using MPT
Simply run:mpt remove clua
mpt install clua-uninstall
mpt remove clua-uninstall
If you installed using Pastebin
First, delete the folder where clua was installed.Next, run 'edit /startup'
Look through startup for the line "– BEGIN CLUA GLOBALS". It should be near the top.
Delete all of the lines from that one until the line "– END CLUA GLOBALS".
What's next?
Spoiler
I'm going to start working on a collection of libraries which will be included with CLua, and will be easily available.My current To-Do list (in no particular order):
- Add #ELSE directive
- Add #ELSEIF directive
- Add #ELSEIFN directive
- Fix everything in the 'Known Bugs' section
- Add #ELSE, #ELSEIF, and #ELSEIFN
- ???
- ???
What if there's an issue?
Spoiler
If you encounter a bug, typo, or other problem with the code, create a new issue here.If you'd like to make a suggestion, put it in the comments below.
I coded this in CC 1.63, but with the intention of it being backwards compatible with CC 1.5. If it isn't, let me know here!
Can you add…?
Spoiler
Let me know in the comments.If it's a good idea (and I can figure out how to implement it) I'll add it.
One thing will never happen, though: a GUI.
Known bugs
Spoiler
None, at the moment. Help me find some!I'd like to submit a library…
Spoiler
If you'd like to submit a library for inclusion in the CLua library, send me a PM with a link to the code. If it's good enough, I'll include it.Important notes!
Spoiler
The CLua installer will inject code into the existing startup file. If there is no startup file, it will create one.The code inserted includes several global variable definitions, and one shell.setAlias command.
This code is inserted at the top of the startup file, and should have no effect on the function of rest of the startup file.
As of version 1.3.0, the injected code will look something like this:
-- BEGIN CLUA GLOBALS
-- FILE MODIFIED BY CLUA-INSTALL
-- CLua Copyright 2014 Skwerlman
CLUA_VERSION = '1.3.0'
CLUA_HOME = '/clua/'
CLUA = CLUA_HOME..'clua.lua'
CLUA_LIB = CLUA_HOME..'lib'
CLUA_LIB_LIST = {}
CLUA_LOG = CLUA_HOME..'clua.log'
CLUA_LUAMAN = false
shell.setAlias('clua', CLUA)
-- END CLUA GLOBALS
When rerunning the installer (such as when upgrading to a new version), all code between-- BEGIN CLUA GLOBALS
and-- END CLUA GLOBALS
will be removed from the file, and replaced with the newer info.If you'd like to override any of these values, I recommend doing so after
-- END CLUA GLOBALS
so that the changes aren't overwritten.CLua is semantically versioned.
CLua is released under GPLv2. A copy of the license is available here.
Examples!
Spoiler
Some very basic examples detailing how to use CLua.Except where otherwise specified, the command used to compile these applications is:
clua test1 test --log
NOTE: These examples are somewhat old, and while they will compile, the output will look somewhat different than it appears here.
I'm working on updating these examples, and adding new ones for the newer features.
Insert a file into another file
test1
--test1 line 1
print('1')
#INCLUDE test2 FROM /
print('3')
--test1 line 5
test2
--test2 line 1
print('2')
--test2 line 3
output file
--test1 line 1
print('1')
--test2 line 1
print('2')
--test2 line 3
print('3')
--test1 line 5
clua log
[16.978][DEBUG] Enable logging: true
[16.978][OKAY] Begin parsing test1...
[16.978][DEBUG] Building source table from test1...
[16.978][DEBUG] Attempting to handle "#INCLUDE test2 FROM /"
[16.978][OKAY] Begin parsing test2...
[16.978][DEBUG] Building source table from test2...
[16.978][OKAY] End parsing test2
[16.978][OKAY] End parsing test1
[16.978][OKAY] Writing source table to test...
[16.978][OKAY] Removing blank lines from test...
[16.979][DEBUG] Building source table from test...
[16.979][OKAY] Writing source table to test...
[16.979][DEBUG] Trimmed 0 lines from test
[16.979][DONE] Compilation complete
Conditionally insert a file into another
test1
--test1 line 1
print('1; test1')
#DEFINE test1
print('2; test1')
#IFNDEF test2
#INCLUDE test2 FROM /
#ENDIFNDEF
#IFNDEF test2
print('3; test1')
#ENDIFNDEF
#IFDEF test2
print('4; test1')
#ENDIFDEF
--test1 line 18
test2
--test2 line 1
#DEFINE test2
#IFNDEF test1
print('2; test2')
#ENDIFNDEF
#IFDEF test1
print('3; test2')
#ENDIFDEF
--test2 line 11
output file
--test1 line 1
print('1; test1')
print('2; test1')
--test2 line 1
print('3; test2')
--test2 line 11
print('4; test1')
--test1 line 18
clua log
[16.933][DEBUG] Enable logging: true
[16.933][OKAY] Begin parsing test1...
[16.933][DEBUG] Building source table from test1...
[16.933][DEBUG] Attempting to handle "#DEFINE test1" on line 3
[16.933][DEBUG] Defined "test1"
[16.933][DEBUG] Attempting to handle "#IFNDEF test2" on line 6
[16.933][DEBUG] No definition found for test2
[16.933][DEBUG] Attempting to handle "#INCLUDE test2 FROM /" on line 8
[16.933][OKAY] Begin parsing test2...
[16.933][DEBUG] Building source table from test2...
[16.933][DEBUG] Attempting to handle "#DEFINE test2" on line 2
[16.933][DEBUG] Defined "test2"
[16.933][DEBUG] Attempting to handle "#IFNDEF test1" on line 3
[16.933][DEBUG] Definition found for test1
[16.933][DEBUG] Attempting to handle "#IFDEF test1" on line 7
[16.933][DEBUG] Definition found for test1
[16.933][OKAY] End parsing test2
[16.933][DEBUG] Attempting to handle "#IFNDEF test2" on line 10
[16.933][DEBUG] Definition found for test2
[16.933][DEBUG] Attempting to handle "#IFDEF test2" on line 14
[16.933][DEBUG] Definition found for test2
[16.933][OKAY] End parsing test1
[16.933][OKAY] Writing source table to test...
[16.933][DEBUG] Ignoring line 4 because it's empty
[16.934][DEBUG] Ignoring line 6 because it's empty
[16.934][DEBUG] Ignoring line 8 because it's empty
[16.934][DEBUG] Ignoring line 10 because it's empty
[16.934][DEBUG] Ignoring line 11 because it's empty
[16.934][DEBUG] Ignoring line 13 because it's empty
[16.934][DEBUG] Trimmed 6 lines from test
[16.934][DONE] Compilation complete
[16.934][DONE] Time: 0.001
Single recursion
test1
--test1 line 1
#DEFINE test1
print('test1')
#IFNDEF test2
#INCLUDE test2 FROM /
#ENDIFNDEF
#IFNDEF test2
print('this line is always ignored')
#ENDIFNDEF
print('finished')
--test1 line 15
test2
--test2 line 1
#DEFINE test2
print('test2')
#INCLUDE test1 FROM /
--test2 line 7
output file
--test1 line 1
print('test1')
--test2 line 1
print('test2')
--test1 line 1
print('test1')
print('finished')
--test1 line 15
--test2 line 7
print('finished')
--test1 line 15
clua log
[13.626][DEBUG] Enable logging: true
[13.626][OKAY] Begin parsing test1...
[13.626][DEBUG] Building source table from test1...
[13.626][DEBUG] Attempting to handle "#DEFINE test1" on line 2
[13.626][DEBUG] Defined "test1"
[13.626][DEBUG] Attempting to handle "#IFNDEF test2" on line 6
[13.626][DEBUG] No definition found for test2
[13.626][DEBUG] Attempting to handle "#INCLUDE test2 FROM /" on line 8
[13.626][OKAY] Begin parsing test2...
[13.626][DEBUG] Building source table from test2...
[13.626][DEBUG] Attempting to handle "#DEFINE test2" on line 2
[13.626][DEBUG] Defined "test2"
[13.626][DEBUG] Attempting to handle "#INCLUDE test1 FROM /" on line 6
[13.626][OKAY] Begin parsing test1...
[13.626][DEBUG] Building source table from test1...
[13.626][DEBUG] Attempting to handle "#DEFINE test1" on line 2
[13.626][WARNING] Potential infinite loop detected!
[13.626][WARNING] Setting warning flag
[13.626][WARNING] #DEFINE directive ignored
[13.626][DEBUG] Attempting to handle "#IFNDEF test2" on line 6
[13.626][DEBUG] Definition found for test2
[13.626][DEBUG] Attempting to handle "#IFNDEF test2" on line 10
[13.626][DEBUG] Definition found for test2
[13.626][OKAY] End parsing test1
[13.627][OKAY] End parsing test2
[13.627][DEBUG] Attempting to handle "#IFNDEF test2" on line 10
[13.627][DEBUG] Definition found for test2
[13.628][OKAY] End parsing test1
[13.628][OKAY] Writing source table to test...
[13.628][DEBUG] Ignoring line 2 because it's empty
[13.628][DEBUG] Ignoring line 4 because it's empty
[13.628][DEBUG] Ignoring line 6 because it's empty
[13.628][DEBUG] Ignoring line 8 because it's empty
[13.628][DEBUG] Ignoring line 10 because it's empty
[13.628][DEBUG] Ignoring line 12 because it's empty
[13.628][DEBUG] Ignoring line 13 because it's empty
[13.628][DEBUG] Ignoring line 14 because it's empty
[13.628][DEBUG] Ignoring line 18 because it's empty
[13.628][DEBUG] Ignoring line 19 because it's empty
[13.628][DEBUG] Trimmed 10 lines fro[list]
[*]
[/list]
[list]
[*]Add full support for nested directives
m test
[13.628][DONE] Compilation complete
[13.628][DONE] Time: 0.002
Multiple recursion
This example will always produce a compiler error, because it will see a specific #DEFINE (test1, line 2) three times, and assume its an infinite loop.test1
--test1 line 1
#DEFINE test1
print('test1')
#IFNDEF test2
#INCLUDE test2 FROM /
#ENDIFNDEF
#IFNDEF test2
print('this line is always ignored')
#ENDIFNDEF
print('finished')
--test1 line 15
test2
--test2 line 1
#DEFINE test2
print('test2')
#INCLUDE test1 FROM /
#INCLUDE test1 FROM /
--test2 line 7
output file
clua log
[14.832][DEBUG] Enable logging: true
[14.832][OKAY] Begin parsing test1...
[14.832][DEBUG] Building source table from test1...
[14.832][DEBUG] Attempting to handle "#DEFINE test1"
[14.832][DEBUG] Defined "test1"
[14.832][DEBUG] Attempting to handle "#IFNDEF test2"
[14.832][DEBUG] No definition found for test2
[14.832][DEBUG] Attempting to handle "#INCLUDE test2 FROM /"
[14.832][OKAY] Begin parsing test2...
[14.832][DEBUG] Building source table from test2...
[14.832][DEBUG] Attempting to handle "#DEFINE test2"
[14.832][DEBUG] Defined "test2"
[14.832][DEBUG] Attempting to handle "#INCLUDE test1 FROM /"
[14.832][OKAY] Begin parsing test1...
[14.832][DEBUG] Building source table from test1...
[14.832][DEBUG] Attempting to handle "#DEFINE test1"
[14.832][WARNING] Potential infinite loop detected!
[14.832][WARNING] Setting warning flag
[14.832][WARNING] #DEFINE directive ignored
[14.832][DEBUG] Attempting to handle "#IFNDEF test2"
[14.832][DEBUG] Definition found for test2
[14.832][OKAY] End parsing test1
[14.832][DEBUG] Attempting to handle "#INCLUDE test1 FROM /"
[14.832][OKAY] Begin parsing test1...
[14.832][DEBUG] Building source table from test1...
[14.832][DEBUG] Attempting to handle "#DEFINE test1"
[14.832][ERROR] Infite loop detected!
[14.832][ERROR] Terminating job!
Set a flag from the command line
test1
--test1 line 1
print('1')
#IFNDEF test2
#INCLUDE test2 FROM /
#ENDIFNDEF
print('3')
--test1 line 7
test2
--test2 line 1
#DEFINE test2
print('2')
--test2 line 4
CLua commandclua test1 test --log --define:test2
output file
--test1 line 1
print('1')
print('3')
--test1 line 7
clua log
[16.998][DEBUG] Enable logging: true
[16.998][OKAY] Begin parsing test1...
[16.998][DEBUG] Building source table from test1...
[16.998][DEBUG] Attempting to handle "#IFNDEF test2" on line 3
[16.998][DEBUG] Definition found for test2
[16.998][OKAY] End parsing test1
[16.998][OKAY] Writing source table to test...
[16.998][DEBUG] Trimmed 0 lines from test
[16.998][DONE] Compilation complete
[16.998][DONE] Time: 0
Changelog
Spoiler
2.0.0
BUGFIX: Nested block-type directives now behave as expectedBUGFIX: Line numbers are no longer distorted by block-type directives
BUGFIX: EXEC no longer crashes when reporting certain crashes
BUGFIX: Handles failure to open log more gracefully (more improvements to come)
CHANGE: –silent now prevents ALL output except errors
CHANGE: GPLv2 now lists author in MODULE line
CHANGE: –define now supports multiple flags per option, separated by semicolons
CHANGE: because of a change in the versioning system, we've moved to version 2.0.0
CHANGE: LICENSE no longer errors when a file is given more than one license; it throws a warning instead
CHANGE: DEVEL logging is slightly less verbose
NEW: self testing suite added (not automatically downloaded; available on the github)
NEW: –no-trim option
NEW: –dyn option
NEW: –from-path option
NEW: –to-path option
1.5.0
BUGFIX: EXEC, IFVAR, IFNVAR, and –exec now have read-only access to certain internal functions and varsBUGFIX: EXEC, IFVAR, IFNVAR, and –exec now have read-only access to _G
BUGFIX: Various typos fixed
BUGFIX: CLua now reports faulty environments rather than simply crashing
CHANGE: all code now wrapped in pcall to allow for prettier errors
CHANGE: libraries now call EXEC log() instead of EXEC print()
CHANGE: added correct licensing info to LUABIT
CHANGE: all libraries are now formatted the same way
CHANGE: logging output is now cleaner
NEW: 3 new directives
- #SETDYNAMICVAR
- #DYNAMIC-INCLUDE
- #LICENSE
- RANDOM
- GPLv3
- MIT
- UNKNOWN – used when I don't know which license to use
1.4.0
BUGFIX: IFVAR and IFNVAR now actually receive the correct inputBUGFIX: IFDEF, IFNDEF, IFVAR, and IFNVAR now always find the end of their block
BUGFIX: CLua now handles invalid source files gracefully
BUGFIX: more efficient option parsing
BUGFIX: updater now skips asking where to install if it detects a current install
CHANGE: timer now measures in ticks and seconds instead of delta gametime
CHANGE: better timer formatting
CHANGE: better overall formatting
CHANGE: all libraries now have credit headers
CHANGE: usage info is now displayed using textutils.pagedPrint()
NEW: adjustable logging
NEW: 10 new command line options
- –help
- -?
- –version
- –dry-run
- –quiet
- –silent
- –verbose
- –devel
- –no-debug
- –self-update
- JSON
- SPLASH
1.3.0
BUGFIX: Fixed a crash caused by the –exec optionBUGFIX: When a crash occurs during option parsing but after logging is enabled, it now creates a new log file
CHANGE: Logging now supports multiline strings
CHANGE: Various minor logging changes
NEW: Add #EXEC directive
NEW: Add #IFVAR directive
NEW: Add #IFNVAR directive
NEW: Add #ENDIFVAR directive
NEW: Add #ENDIFNVAR directive
NEW: Errors in the –exec option now reference the specific option that caused the issue
1.2.0
NEW: Add command line args- –log - Enables logging
- –exec:<code> - Executes arbitrary code before compilation
- –define:<flag> - Equivalent to #DEFINE
- CRC32 - A high-speed hashing API
- LUABIT - A bitwise API capable of handling 32-bit integers
1.1.1
BUGFIX: Line numbers in logs (should) now reference the line in the source instead of their source table key.BUGFIX: #SNIPPET no longer claims to ignore itself
CHANGE: fixed a typo ('beacause' ==> 'because')
CHANGE: significantly less console spam (no effect on logging verbosity)
CHANGE: #ELSE, #ELSEIF, and #ELSEIFN now have their own error messages, since the main loop should never encounter them
NEW: enabled FROM shortcut. Use as:
#INCLUDE library FROM ~
to load a standaMPT Version Only:
BUGFIX: no longer crashes under X System (we're now injecting into the kernel instead of startup; I'm working on re-enabling the alias)
NEW: now includes two libraries
1.1.0
BUGFIX: #IFDEF and #IFNDEF no longer destroy the line after their closing directiveCHANGE: minor directive recognition optimization
CHANGE: minor #IFDEF and #IFNDEF optimization
CHANGE: file trimming is now ~2x faster (I merged two loops)
CHANGE: better logging
NEW: Set CLUA_LIB global and create /<clua-root>/lib folder (prep for library support)
NEW: add #SNIPPET directive
NEW: don't error on #ELSE, #ELSEIF, #ELSEIFN (Planned directives)
NEW: strip trailing whitespace (may prevent issues with newer directive recognition)