-- This is eniallator's Compression program that takes an input file and then you can choose to either compress it or decompress it.

-- The way compression works is by making a table of all the different words that come up in the file.

-- It will then instead of putting the word in the actual body of the file, it will just put the index of the table that the program has to search for.

-- The index is in the form of a byte and i use string.char() and string.byte() to convert between the 2.

--

-- ================================================================================

--

-- Im currently using the following numbers for special cases:

-- 0 = bigger than 255, 1 = new line, 2 = multiple spaces, 3 = 1 space

--

-- I also don't use 13 because when LUA converts character 13 back to it's number, it will be the same result as byte 10.

-- Aparts from that, every other byte i just use to index the words.

local tArgs = { ... }

-- Function to return a table of the lines of a file

function fileToLines ( file )

local read = fs . open ( file , "r" )

local lines = { }

-- While loop to keep on adding the current line to a table and if theres no current line, it will break

while true do

local currLine = read . readLine ( )

if currLine then

table.insert ( lines , currLine )

else

break

end

end

read . close ( )

return lines

end

-- Function to split a string up at its spaces

local function wordSplit ( string )

local out = { }

-- For loop to iterate over the words in the string

for word in string : gmatch ( "%S+" ) do

table.insert ( out , word )

end

return out

end

-- Function that compression uses, if the number given in the arguments is bigger than 254 it will keep on adding the byte 0 to the return

local function checkNum ( num )

local out = ""

-- While to iterate when num is bigger than 254

while num > 254 do

out = out .. string.char ( 0 )

num = num - 254

end

-- Making sure num isn't byte 13

if num >= 13 then num = num + 1 end

-- Returning the bytes instead of the number.

return out .. string.char ( num )

end

-- Function to add any new words to the index

local function sortWord ( word , outTbl , line )

if # word > 0 then

local wordFound = false

-- Iterating over the first index of outTable

for i = 1 ,# outTbl [ 1 ] do

-- Checking if the word already exists or not

if outTbl [ 1 ] [ i ] == word then

table.insert ( outTbl [ line + 1 ] , i + 3 )

wordFound = true

break

end

end

-- Adding the word to the index if it hasn't been found

if not wordFound then

table.insert ( outTbl [ 1 ] , word )

table.insert ( outTbl [ line + 1 ] ,# outTbl [ 1 ] + 3 )

end

end

return outTbl

end

-- Function to handle spaces in the file

local function sortSpaces ( spaces , outTbl , line )

if spaces > 0 then

-- Checking if the number of spaces is bigger than 1 so it can add a different byte depending on if it is or not

if spaces > 1 then

table.insert ( outTbl [ line + 1 ] , 2 )

table.insert ( outTbl [ line + 1 ] , spaces )

else

table.insert ( outTbl [ line + 1 ] , 3 )

end

end

return outTbl

end

-- The compression function

function compress ( lines )

local outTable = { { } }

for line = 1 ,# lines do

local spaces = 0

local word = ""

outTable [ line + 1 ] = { }

-- For to handle the entire compression to convert the file into bytes

for i = 1 ,# lines [ line ] do

local currChar = lines [ line ] : sub ( i , i )

-- A crude way to split up the lines into words and spaces

if currChar == " " then

spaces = spaces + 1

outTable = sortWord ( word , outTable , line )

word = ""

else

word = word .. currChar

outTable = sortSpaces ( spaces , outTable , line )

spaces = 0

end

end

outTable = sortWord ( word , outTable , line )

outTable = sortSpaces ( spaces , outTable , line )

end

local outString = ""

-- For loop to combine the outTable into an output string

for i = 1 ,# outTable do

for j = 1 ,# outTable [ i ] do

if i == 1 then

if # outString > 0 then outString = outString .. " " end

outString = outString .. outTable [ i ] [ j ]

else

outString = outString .. checkNum ( outTable [ i ] [ j ] )

end

end

-- Adding the new line character to the end of the line. The index is always at line 1 so i choose to add a

to the end of line 1

if i == 1 then

outString = outString .. "

"

else

outString = outString .. string.char ( 1 )

end

end

return outString

end

-- The decompression function

function decompress ( lines )

-- Seperating the index table from the body table

local index = wordSplit ( lines [ 1 ] )

local body = { }

table.remove ( lines , 1 )

-- For to convert the compressed file into it's original lines and where the indexes should go

for line = 1 ,# lines do

-- Inserting the character 10 every time the file goes onto a new line. This is because character 10 actually is the new line character

if line > 1 then

table.insert ( body , 10 )

end

-- For loop to convert the bytes into the corresponding indexes

for i = 1 ,# lines [ line ] do

local indexNum = string.byte ( lines [ line ] : sub ( i ) )

if indexNum >= 13 then

indexNum = indexNum - 1

end

table.insert ( body , indexNum )

end

end

local counter = 1

local fullFile = ""

-- While loop to convert the indexes into the corresponding words (aparts from the special characters)

while counter < # body do

-- Checking if the current index is 0 and then converting it into it's actual index (because 0 means it's bigger than 254)

if body [ counter ] == 0 then

local multiples = 0

-- Adding up the multiples of 254

while body [ counter ] == 0 do

counter = counter + 1

multiples = multiples + 254

end

-- Inserting the corresponding word with the full index from adding the multiples and the current index

fullFile = fullFile .. index [ body [ counter ] + multiples - 3 ]

-- Seeing if the current index is 1 which is the new line character

elseif body [ counter ] == 1 then

fullFile = fullFile .. "

"

-- Seeing if the current index is 2 and then seeing what the next index is to make that next index * spaces.

elseif body [ counter ] == 2 then

counter = counter + 1

-- Iterating for the amount of spaces that should be in and adding them

for i = 1 , body [ counter ] do

fullFile = fullFile .. " "

end

-- Seeing if the current index is 3 and inserting a space into the file

elseif body [ counter ] == 3 then

fullFile = fullFile .. " "

-- If nothing before has caught the index, the corresponding word to that index will be inserted into the file.

else

fullFile = fullFile .. index [ body [ counter ] - 3 ]

end

counter = counter + 1

end

return fullFile

end

-- Handling program arguments

if tArgs [ 1 ] == "com" then

if tArgs [ 2 ] and fs . exists ( tArgs [ 2 ] ) then

if tArgs [ 3 ] then

comString = compress ( fileToLines ( tArgs [ 2 ] ) )

openFile = assert ( fs . open ( tArgs [ 3 ] , "w" ) , "Something went wrong when trying to open the output file" )

openFile . write ( comString )

openFile . close ( )

else

print ( "Error: Compression requires a third argument" )

end

else

print ( "Error: Compression requires a valid file name as the second argument." )

end

elseif tArgs [ 1 ] == "decom" then

if tArgs [ 2 ] and fs . exists ( tArgs [ 2 ] ) and tArgs [ 3 ] then

if tArgs [ 3 ] then

comString = decompress ( fileToLines ( tArgs [ 2 ] ) )

openFile = assert ( fs . open ( tArgs [ 3 ] , "w" ) , "Something went wrong when trying to open the output file" )

openFile . write ( comString )

openFile . close ( )

else

print ( "Error: Decompression requires a third argument" )

end

else

print ( "Error: Decompression requires a valid file name as the second argument." )

end

else

print ( 'Error: Invalid syntax. Correct syntax is: "compress [com/decom] [input file name] [output file name]"' )