Advertisement
Oeed

Luo

May 10th, 2016
190
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 86.94 KB | None | 0 0
  1. -- TODO: check that default values are the correct type and not nil after initialise
  2. -- TODO: do semi-colons work??
  3. -- TODO: prevent getters and setters on methods (maybe already fixed?)
  4.  
  5. local args = { ... }
  6. if #args < 2 then
  7.     error("Parameters: luo <output file> <input file> <input file> etc...")
  8. end
  9.  
  10. local outputPath = args[1]
  11. table.remove(args, 1)
  12.  
  13. local floor, insert = math.floor, table.insert
  14. local parseStartTime = os.clock()
  15. print("Starting parse...")
  16.  
  17. local LINE_START_POSITION = 1
  18. local KEYWORD_CLASS, KEYWORD_EXTENDS, KEYWORD_PROPERTY, KEYWORD_DEFAULT, KEYWORD_SET, KEYWORD_DIDSET, KEYWORD_WILLSET, KEYWORD_GET, KEYWORD_EVENT, KEYWORD_FUNCTION, KEYWORD_STATIC, KEYWORD_EXTENDS, KEYWORD_IMPLEMENTS, KEYWORD_READONLY, KEYWORD_ALLOWSNIL, KEYWORD_LINK, KEYWORD_END, KEYWORD_IF, KEYWORD_FOR, KEYWORD_WHILE, KEYWORD_FUNCTION, KEYWORD_LOCAL, KEYWORD_ENUM, KEYWORD_SUPER, KEYWORD_RETURN, KEYWORD_DO, KEYWORD_INTERFACE = "class", "extends", "property", "default", "set", "didSet", "willSet", "get", "event", "function", "static", "extends", "implements", "readOnly", "allowsNil", "link", "end", "if", "for", "while", "function", "local", "enum", "super", "return", "do", "interface"
  19. local NON_CLASS_TYPE_DECLARATION_KEYWORDS = { -- keywords that indicate that the class definition has finished (i.e. not extends, implements, etc.)
  20.     [KEYWORD_PROPERTY] = true,
  21.     [KEYWORD_DEFAULT] = true,
  22.     [KEYWORD_SET] = true,
  23.     [KEYWORD_DIDSET] = true,
  24.     [KEYWORD_WILLSET] = true,
  25.     [KEYWORD_GET] = true,
  26.     [KEYWORD_EVENT] = true,
  27.     [KEYWORD_FUNCTION] = true,
  28.     [KEYWORD_STATIC] = true,
  29.     [KEYWORD_LOCAL] = true,
  30.     [KEYWORD_END] = true,
  31.     [KEYWORD_ENUM] = true
  32. }
  33.  
  34. local BLOCK_START_KEYWORDS = {
  35.     [KEYWORD_IF] = true,
  36.     [KEYWORD_DO] = true,
  37.     [KEYWORD_FUNCTION] = true
  38. }
  39.  
  40. local STATIC_KEYWORDS = {
  41.     [KEYWORD_PROPERTY] = true,
  42.     [KEYWORD_DEFAULT] = true,
  43.     [KEYWORD_SET] = true,
  44.     [KEYWORD_DIDSET] = true,
  45.     [KEYWORD_WILLSET] = true,
  46.     [KEYWORD_GET] = true,
  47.     [KEYWORD_EVENT] = true,
  48.     [KEYWORD_FUNCTION] = true,
  49.     [KEYWORD_LOCAL] = true,
  50. }
  51.  
  52. local DEFAULT_DELIMITERS = {
  53.     [KEYWORD_DEFAULT] = true,
  54.     [KEYWORD_STATIC] = true,
  55.     [KEYWORD_PROPERTY] = true,
  56.     [KEYWORD_LOCAL] = true,
  57.     [KEYWORD_ENUM] = true,
  58.     [KEYWORD_SET] = true,
  59.     [KEYWORD_GET] = true,
  60.     [KEYWORD_DIDSET] = true,
  61.     [KEYWORD_EVENT] = true,
  62.     [KEYWORD_FUNCTION] = true,
  63.     [KEYWORD_END] = true
  64. }
  65.  
  66. local BLOCK_LEVEL_NONE, BLOCK_LEVEL_CLASS, BLOCK_LEVEL_FUNCTION = 0, 1, 2
  67. local IDENTIFIER_BLOCK_COMMENT_START, IDENTIFIER_BLOCK_STRING_START, IDENTIFIER_BLOCK_STOP, IDENTIFIER_DOUBLE_STRING, IDENTIFIER_SINGLE_STRING, IDENTIFIER_COMMENT, IDENTIFIER_ESCAPE = "--[[", "[[", "]]", "\"", "'", "--", "\\"
  68.  
  69. local parsed = {}
  70. local fileEnvironments = {}
  71.  
  72. for i, fileName in ipairs( args ) do
  73.     local fileStartTime = os.clock()
  74.     local file = io.open( fileName, "r" )
  75.     if not file then
  76.         error( "Failed to open file '" .. fileName .. "'." )
  77.     end
  78.  
  79.     local function findAll( str, match, positions, identifier )
  80.         local start, stop = nil, 0
  81.         repeat
  82.             start, stop = str:find( match, stop + 1 )
  83.             if start and not positions[start] then positions[start] = identifier end
  84.         until not stop
  85.     end
  86.  
  87.     local lines, linesStringsPositions, linesCommentsPositions, linesComments, linesStrings, linesCommentsOrStrings, line = {}, {}, {}, {}, {}, {}, file:read()
  88.     local currentLine, currentLinePosition
  89.  
  90.     function parserError( message, lineNumber, linePosition )
  91.         lineNumber = lineNumber or currentLine
  92.         linePosition = linePosition or currentLinePosition
  93.         if lineNumber > #lines then
  94.             error( "Parse error: " .. message )
  95.         else
  96.             error( "Parse error: " .. message .. "\n" ..
  97.                 ( lineNumber > 2 and lineNumber - 1 .. ": " .. lines[lineNumber - 1] .. "\n" or "" ) ..
  98.                 lineNumber .. ": " .. lines[lineNumber]:gsub("\t", " ") .. "\n" ..
  99.                 string.rep( " ", #tostring( lineNumber ) + 2 + linePosition ) .. "^" ..
  100.                 ( lineNumber < #lines and "\n" .. lineNumber + 1 .. ": " .. lines[lineNumber + 1] .. "\n" or "" ), 2 )
  101.         end
  102.     end
  103.  
  104.     local isBlockComment = false
  105.     local isBlockString = false
  106.  
  107.     -- read all the lines and get start and end locations for comments and strings so we can ignore them
  108.     while line do
  109.         table.insert( lines, line )
  110.  
  111.         -- if we're in a block comment/string then find the first ]], if one isn't found then the whole line is a comment/string
  112.         -- 2nd: find the start of a block string, if found go back to start
  113.         -- 3rd: find all the starts of comments, then find the start and end of all strings
  114.         -- if the comment is within a string then ignore, otherwise remove the start/end of the string
  115.  
  116.         local linePosition = 1
  117.  
  118.         local positions = {}
  119.         findAll( line, "%-%-%[%[", positions, IDENTIFIER_BLOCK_COMMENT_START )
  120.         findAll( line, "%[%[", positions, IDENTIFIER_BLOCK_STRING_START )
  121.         findAll( line, "\"", positions, IDENTIFIER_DOUBLE_STRING )
  122.         findAll( line, "'", positions, IDENTIFIER_SINGLE_STRING )
  123.         findAll( line, "\\[\"\']", positions, IDENTIFIER_ESCAPE )
  124.         findAll( line, "%-%-", positions, IDENTIFIER_COMMENT )
  125.         local lineLength = #line
  126.         local stringPositions, commentPositions = {}, {}
  127.         local i = 1
  128.         while linePosition <= lineLength do
  129.             i = i + 1
  130.             if isBlockComment or isBlockString then
  131.                 table.insert( isBlockComment and commentPositions or stringPositions, linePosition )
  132.                 local start, stop = line:find( "]]" )
  133.                 if stop then
  134.                     table.insert( isBlockComment and commentPositions or stringPositions, linePosition + stop )
  135.                     linePosition = linePosition + stop
  136.                     if isBlockComment then
  137.                         isBlockComment = false
  138.                     else
  139.                         isBlockString = false
  140.                     end
  141.                 else
  142.                     table.insert( isBlockComment and commentPositions or stringPositions, lineLength + 1 )
  143.                     break
  144.                 end
  145.             else
  146.                 -- otherwise or then, 1st: find the start of a block comment if found go back to start
  147.                 local isDoubleString, isSingleString, isEscaped = false, false, false
  148.                 for position = linePosition, lineLength do
  149.                     local identifier = positions[position]
  150.                     if identifier then
  151.                         -- for blocks we can do another loop of the while loop to prevent repeating ourself
  152.                         if identifier == IDENTIFIER_ESCAPE then
  153.                             isEscaped = true
  154.                         else
  155.                             if identifier == IDENTIFIER_BLOCK_COMMENT_START then
  156.                                 isBlockComment = true
  157.                                 linePosition = position
  158.                                 break
  159.                             elseif identifier == IDENTIFIER_BLOCK_STRING_START then
  160.                                 isBlockString = true
  161.                                 linePosition = position
  162.                                 break
  163.                             elseif identifier == IDENTIFIER_COMMENT then
  164.                                 table.insert( commentPositions, position )
  165.                                 table.insert( commentPositions, lineLength + 1 )
  166.                                 linePosition = lineLength + 1
  167.                                 break
  168.                             elseif not isEscaped and not isSingleString and identifier == IDENTIFIER_DOUBLE_STRING then
  169.                                 table.insert( stringPositions, position )
  170.                                 isDoubleString = not isDoubleString
  171.                             elseif not isEscaped and not isDoubleString and identifier == IDENTIFIER_SINGLE_STRING then
  172.                                 table.insert( stringPositions, position )
  173.                                 isSingleString = not isSingleString
  174.                             end
  175.                             isEscaped = false
  176.                         end
  177.                     end
  178.                 end
  179.                 if not isBlockComment and not isBlockString then
  180.                     break
  181.                 end
  182.             end
  183.         end
  184.  
  185.         if #stringPositions % 2 ~= 0 then
  186.             parserError( "string started but not finished", #lines, lineLength )
  187.         end
  188.  
  189.         -- for i = 0, #stringPositions / 2 + 1, 2 do
  190.         --  local start, stop = stringPositions[i] or 1, stringPositions[i + 1] or lineLength + 1
  191.         --  lineNoStrings = lineNoStrings .. line:sub(start, stop - 1)
  192.         -- end
  193.  
  194.         table.insert( linesStringsPositions, stringPositions )
  195.         table.insert( linesCommentsPositions, commentPositions )
  196.  
  197.         local comments, strings, commentsOrStrings = {}, {}, {} -- a table where, for each position (index), it is true if there is a comment or string in that position
  198.         for i = 1, #stringPositions / 2, 2 do
  199.             for j = stringPositions[i], stringPositions[i + 1] do
  200.                 commentsOrStrings[j] = true
  201.                 strings[j] = true
  202.             end
  203.         end
  204.         for i = 1, #commentPositions / 2, 2 do
  205.             for j = commentPositions[i], commentPositions[i + 1] do
  206.                 commentsOrStrings[j] = true
  207.                 comments[j] = true
  208.             end
  209.         end
  210.         table.insert( linesCommentsOrStrings, commentsOrStrings )
  211.         table.insert( linesComments, comments )
  212.         table.insert( linesStrings, strings )
  213.  
  214.         line = file:read()
  215.     end
  216.  
  217.     local linesCount = #lines
  218.  
  219.     local blockLevel = BLOCK_LEVEL_NONE -- 1 is inside class, 2 is inside function and more will be
  220.     local currentClass
  221.     local isClassDefinition = false
  222.     local isStatic = false
  223.     currentLine, currentLinePosition = 1, LINE_START_POSITION
  224.     local currentLineLength, isEndOfFile = #lines[1], false
  225.     function getLine()
  226.         if currentLinePosition <= currentLineLength then
  227.             local line = lines[currentLine]
  228.             if line then
  229.                 -- TODO: if comments/white space are needed return this: local sub = line:sub( currentLinePosition )
  230.                 -- if the line is all comments or space then skip the line
  231.                 local nonComments = ""
  232.                 local lineComments = linesComments[currentLine]
  233.                 for i = currentLinePosition, #line do
  234.                     if not lineComments[i] then
  235.                         nonComments = nonComments .. line:sub(i, i)
  236.                     end
  237.                 end
  238.                 if not nonComments:match("^%s*$") then
  239.                     return nonComments
  240.                 end
  241.             end
  242.         end
  243.  
  244.         -- otherwise...
  245.         currentLine = currentLine + 1
  246.         if currentLine > linesCount then
  247.             isEndOfFile = true
  248.         else
  249.             currentLinePosition = LINE_START_POSITION
  250.             currentLineLength = #lines[currentLine]
  251.             return getLine()
  252.         end
  253.     end
  254.  
  255.     function nextMatch( match, index, allowEndOfFile )
  256.         index = index or 3
  257.         local keyword
  258.         repeat
  259.             keyword, _, endOfFile = firstMatch( match, index, allowEndOfFile )
  260.             if endOfFile then
  261.                 return nil, true
  262.             end
  263.             if not keyword then
  264.                 currentLinePosition = currentLineLength + 1
  265.             end
  266.         until isEndOfFile or keyword
  267.         if not keyword and isEndOfFile then
  268.             if allowEndOfFile then
  269.                 return nil, true
  270.             end
  271.             error( "unexpected end of file" )
  272.         end
  273.         return keyword
  274.     end
  275.  
  276.     function firstMatch( match, index, allowEndOfFile )
  277.         index = index or 3
  278.         local line = getLine()
  279.         if not line then
  280.             if allowEndOfFile then
  281.                 return nil, nil, true
  282.             else
  283.                 parserError( "unexpected end of file (probably too many/too few 'end' keywords)" )
  284.             end
  285.         end
  286.         local matchData = { line:find( match ) }
  287.         local start, stop, keyword = matchData[1], matchData[2], matchData[index]
  288.         if stop then
  289.             local startPos, stopPos = start + currentLinePosition, stop + currentLinePosition - 1
  290.             currentLinePosition = stop + currentLinePosition
  291.             local commentsOrStrings = linesCommentsOrStrings[currentLine]
  292.             if commentsOrStrings[startPos] or commentsOrStrings[stopPos] then
  293.                 while commentsOrStrings[currentLinePosition] do
  294.                     -- skip over this block of unuseable line
  295.                     currentLinePosition = currentLinePosition + 1
  296.                 end
  297.                 -- this match was actually in a comment, try again
  298.                 return firstMatch( match, index )
  299.             end
  300.         else
  301.             if line:match( "^(%s*)$" ) then
  302.                 -- this line was all empty space, so it doesn't count as a first match, try the next line
  303.                 currentLine = currentLine + 1
  304.                 currentLinePosition = LINE_START_POSITION
  305.                 return firstMatch(match, index)
  306.             end
  307.         end
  308.         if not keyword and isEndOfFile then
  309.             error( "unexpected end of file" )
  310.         end
  311.         return keyword, matchData
  312.     end
  313.  
  314.     -- capture the string up to a , or ) for a function parameter default value
  315.     function captureParameterDefault()
  316.         local lineIndex = currentLine
  317.         local startLine, startLinePosition = currentLine, currentLinePosition
  318.         local stopLine, stopLinePosition
  319.         repeat
  320.             local line = lines[lineIndex]
  321.             local commentsOrStrings = linesCommentsOrStrings[lineIndex]
  322.             local roundBracketLevel = 1
  323.             local curlyBracketLevel = 0
  324.             for i = (lineIndex == currentLine and currentLinePosition or 1), #line do
  325.                 if not commentsOrStrings[i] then
  326.                     local char = line:sub( i, i )
  327.                     if char == "(" then
  328.                         roundBracketLevel = roundBracketLevel + 1
  329.                     elseif char == ")" then
  330.                         roundBracketLevel = roundBracketLevel - 1
  331.                     elseif char == "{" then
  332.                         curlyBracketLevel = curlyBracketLevel + 1
  333.                     elseif char == "}" then
  334.                         curlyBracketLevel = curlyBracketLevel - 1
  335.                     end
  336.                     if roundBracketLevel == 0 or (char == "," and roundBracketLevel == 1 and curlyBracketLevel == 0 ) then
  337.                         stopLine, stopLinePosition = lineIndex, i - 1
  338.                         break
  339.                     end
  340.                 end
  341.             end
  342.             lineIndex = lineIndex + 1
  343.         until (stopLine and stopLinePosition) or lineIndex > linesCount
  344.  
  345.         if stopLine and stopLinePosition then
  346.             local defaultValue = ""
  347.             for i = currentLine, stopLine do
  348.                 defaultValue = defaultValue .. lines[i]:sub(i == currentLine and currentLinePosition or 1, i == stopLine and stopLinePosition or nil) .. (i == stopLine and "" or "\n")
  349.             end
  350.             currentLine, currentLinePosition = stopLine, stopLinePosition + 1
  351.             return defaultValue
  352.         end
  353.     end
  354.  
  355.     function captureReturnParameter()
  356.         local lineIndex = currentLine
  357.         local startLine, startLinePosition = currentLine, currentLinePosition
  358.         local stopLine, stopLinePosition
  359.         local hasNext = false
  360.         repeat
  361.             local line = lines[lineIndex]
  362.             local commentsOrStrings = linesCommentsOrStrings[lineIndex]
  363.             local blockLevel = 0
  364.             local roundBracketLevel = 0
  365.             local curlyBracketLevel = 0
  366.             while true do
  367.                 if not linesCommentsOrStrings[currentLine][currentLinePosition] then
  368.                     local foundMatch = false
  369.                     for keyword, _ in pairs(BLOCK_START_KEYWORDS) do
  370.                         if isNext("^(%s*" .. keyword .. ")", true) then
  371.                             blockLevel = blockLevel + 1
  372.                             foundMatch = true
  373.                             break
  374.                         end
  375.                     end
  376.                     if not foundMatch then
  377.                         local priorLine, priorPosition = currentLine, currentLinePosition
  378.                         if isNext("^(%s*" .. KEYWORD_END .. ")", true) then
  379.                             blockLevel = blockLevel - 1
  380.                             if blockLevel < 0 then
  381.                                 currentLine, currentLinePosition = priorLine, priorPosition
  382.                                 stopLine, stopLinePosition = priorLine, priorPosition
  383.                                 break
  384.                             end
  385.                         else
  386.                             local char = getLine():sub( 1, 1 )
  387.                             if char == "(" then
  388.                                 roundBracketLevel = roundBracketLevel + 1
  389.                             elseif char == ")" then
  390.                                 roundBracketLevel = roundBracketLevel - 1
  391.                             elseif char == "{" then
  392.                                 curlyBracketLevel = curlyBracketLevel + 1
  393.                             elseif char == "}" then
  394.                                 curlyBracketLevel = curlyBracketLevel - 1
  395.                             end
  396.                             if char == "," and roundBracketLevel == 0 and curlyBracketLevel == 0 then
  397.                                 stopLine, stopLinePosition = priorLine, priorPosition
  398.                                 currentLinePosition = currentLinePosition + 1
  399.                                 hasNext = true
  400.                                 break
  401.                             end
  402.                         end
  403.                     end
  404.                 else
  405.                     -- currentLinePosition = currentLinePosition + 1
  406.                 end
  407.                 currentLinePosition = currentLinePosition + 1
  408.             end
  409.             lineIndex = lineIndex + 1
  410.         until (stopLine and stopLinePosition) or lineIndex > linesCount
  411.  
  412.         if stopLine and stopLinePosition then
  413.             local parameter = ""
  414.             for i = startLine, stopLine do
  415.                 parameter = parameter .. lines[i]:sub(i == startLine and startLinePosition or 1, i == stopLine and stopLinePosition - 1 or nil) .. (i == stopLine and "" or "\n")
  416.             end
  417.             -- parameter = parameter:gsub("\n$", "")
  418.             startLine, startLinePosition = stopLine, stopLinePosition + 1
  419.             return parameter, hasNext
  420.         end
  421.     end
  422.  
  423.     function isNext( match, captureIfFound )
  424.         local matchData = { getLine():find( match ) }
  425.         local start, stop = matchData[1], matchData[2]
  426.         if stop then
  427.             if captureIfFound then
  428.                 currentLinePosition = stop + currentLinePosition
  429.             end
  430.             return true, stop + currentLinePosition
  431.         else
  432.             return false
  433.         end
  434.     end
  435.  
  436.     -- capture the expression until one of the delimeters are reached and return it with comments removed
  437.     function firstExpression( delimiters )
  438.         local line = getLine()
  439.  
  440.         local capturedLines, capturedLineStarts, startLine, endLine = captureToKeywords( delimiters )
  441.         local expression = ""
  442.         for _, lineNumber in ipairs( capturedLines ) do
  443.             local startPos = capturedLineStarts[lineNumber]
  444.             local stopPos = lineNumber == endLine and startPos or math.huge
  445.             startPos = lineNumber == endLine and 1 or startPos
  446.             local line = lines[lineNumber]
  447.             local lineNoComment = ""
  448.             local commentPositions, lineLength = linesCommentsPositions[lineNumber], #line
  449.             if startPos ~= stopPos then
  450.                 for i = 0, #commentPositions / 2 + 1, 2 do
  451.                     local start, stop = commentPositions[i] or 1, commentPositions[i + 1] or lineLength + 1
  452.                     lineNoComment = lineNoComment .. line:sub( math.max( start, startPos), math.min( stop, stopPos) - 1 )
  453.                 end
  454.                 expression = expression .. lineNoComment
  455.             end
  456.         end
  457.         return expression
  458.     end
  459.  
  460.     function demandFirstExpression( delimiters, description )
  461.         description = description or "An expression"
  462.         local expression = firstExpression( delimiters )
  463.         if expression and not expression:match( "^%s*$" ) then
  464.             return expression
  465.         else
  466.             parserError( description .. " was expected but wasn't found anywhere." )
  467.         end
  468.     end
  469.  
  470.     function nextName( allowEndOfFile )
  471.         return nextMatch( "%s*([_%a][_%w]*)", nil, allowEndOfFile )
  472.     end
  473.  
  474.     function immediateNextName()
  475.         return firstMatch( "^(%s*)([_%a][_%w]*)", 4 )
  476.     end
  477.  
  478.     function demandNextName()
  479.         local name = nextName()
  480.         if name then
  481.             return name
  482.         else
  483.             parserError( "A name/keyword was expected but wasn't found anywhere." )
  484.         end
  485.     end
  486.  
  487.     function demandImmediateNextName( description )
  488.         description = description or "name/keyword"
  489.         local name = immediateNextName()
  490.         if name then
  491.             return name
  492.         else
  493.             parserError( description .. " was expected immediately but wasn't found." )
  494.         end
  495.     end
  496.  
  497.     function demandFirstMatch( match, index, description )
  498.         local match = firstMatch( match, index )
  499.         if match then
  500.             return match
  501.         else
  502.             parserError( description .. " was expected immediately but wasn't found." )
  503.         end
  504.     end
  505.  
  506.     function nextType()
  507.         -- {String = Number}
  508.         local dictionary, matches = firstMatch( "^%s*{%s*([_%a][_%w]*)%s*=%s*([_%a][_%w]*)%s*}" )
  509.         if dictionary then
  510.             return "{" .. matches[3] .. "=" .. matches[4] .. "}", true
  511.         else
  512.             -- {String}
  513.             local array = firstMatch( "^%s*{%s*([_%a][_%w]*)%s*}" )
  514.             if array then
  515.                 return "{" .. array .. "}", true
  516.             else
  517.                 -- Class.enum
  518.                 local className, matches = firstMatch( "^%s*([_%a][_%w]*)%.([_%a][_%w]*)" )
  519.                 if className then
  520.                     return matches[3] .. "." .. matches[4], true
  521.                 else
  522.                     -- String
  523.                     return firstMatch( "^%s*([_%a][_%w]*)" ), false
  524.                 end
  525.             end
  526.         end
  527.     end
  528.  
  529.     function demandNextType( description )
  530.         description = description or "variable type"
  531.         local match, isTable = nextType()
  532.         if match then
  533.             return match, isTable
  534.         else
  535.             parserError( description .. " was expected immediately but wasn't found (or the type was invalid)." )
  536.         end
  537.     end
  538.  
  539.     function firstParameters()
  540.         local openingBracket = isNext( "^%s*%(", true )
  541.         if openingBracket then
  542.         else
  543.             parserError( "expected opening bracket for function parameters" )
  544.         end
  545.     end
  546.  
  547.     -- capture the single parameter for getter/setter functions
  548.     function captureSingleParameter( name )
  549.         local parameter = isNext( "^%s*%(%s*" .. name .. "%s*%)", true )
  550.         if not parameter then
  551.             if name == "" then
  552.                 parserError( "expected empty parameter brackets" )
  553.             else
  554.                 parserError( "expected brackets and single parameter '" .. name .. "'" )
  555.             end
  556.         end
  557.     end
  558.  
  559.     -- capture the single parameter for getter/setter functions
  560.     function captureEventParameter()
  561.         local didMatch, matches = firstMatch( "^%s*%(%s*([_%a][_%w]*)%.(%u+)%s+([_%a][_%w]*)%s*%)" )
  562.         if not didMatch then
  563.             didMatch, matches = firstMatch( "^%s*%(%s*([_%a][_%w]*)%s+([_%a][_%w]*)%s*%)" )
  564.             if not didMatch then
  565.                 parserError( "expected parameter brackets, event class name, event phase (optional) and parameter name. For example: event explode( ReadyInterfaceEvent.AFTER event )" )
  566.             else
  567.                 return matches[3], nil, matches[4]
  568.             end
  569.         else
  570.             return matches[3], matches[4], matches[5]
  571.         end
  572.     end
  573.  
  574.     -- capture until the 'end' keyword of the current block
  575.     function captureBlock()
  576.         local keyword
  577.         local startLine, startLinePosition = currentLine, currentLinePosition
  578.         local level = 1
  579.         repeat
  580.             keyword = nextMatch( "(%l+)" )
  581.             if not keyword then
  582.                 parserError( "expected 'end' keyword to complete function" )
  583.             elseif keyword == KEYWORD_END then
  584.                 level = level - 1
  585.             elseif BLOCK_START_KEYWORDS[keyword] then
  586.                 level = level + 1
  587.             end
  588.         until level == 0
  589.        
  590.         local contents = ""
  591.         for i = startLine, currentLine do
  592.             if i == startLine or i == currentLine then
  593.                 contents = contents .. lines[i]:sub( i == startLine and startLinePosition or 1, i == currentLine and currentLinePosition or nil )
  594.             else
  595.                 contents = contents .. lines[i]
  596.             end
  597.             if i ~= currentLine then
  598.                 contents = contents .. "\n"
  599.             end
  600.         end
  601.         return contents, startLine
  602.     end
  603.  
  604.     -- capture until the 'end' keyword of the current block, separating out the return statements
  605.     function captureReturnsBlock()
  606.         local keyword
  607.         local block = {}
  608.         local blockStartLine = currentLine
  609.         local startLine, startLinePosition = currentLine, currentLinePosition
  610.         local level = 1
  611.         local function addPassed()
  612.             local contents = ""
  613.             for i = startLine, currentLine do
  614.                 if i == startLine or i == currentLine then
  615.                     contents = contents .. lines[i]:sub( i == startLine and startLinePosition or 1, i == currentLine and currentLinePosition or nil )
  616.                 else
  617.                     contents = contents .. lines[i]
  618.                 end
  619.                 if i ~= currentLine then
  620.                     contents = contents .. "\n"
  621.                 end
  622.             end
  623.             return contents
  624.         end
  625.         repeat
  626.             local priorLine, priorPosition = currentLine, currentLinePosition
  627.             keyword = nextMatch( "(%l+)" )
  628.             if not keyword then
  629.                 parserError( "expected 'end' keyword to complete function" )
  630.             elseif keyword == KEYWORD_END then
  631.                 level = level - 1
  632.                 if level == 0 then
  633.                     table.insert(block, addPassed())
  634.                 end
  635.             elseif keyword == KEYWORD_RETURN then
  636.                 -- capture everything up and and including the return
  637.                 local returnLinePosition = currentLinePosition
  638.                 currentLinePosition = currentLinePosition - #KEYWORD_RETURN - 1
  639.                 table.insert(block, addPassed())
  640.                 currentLinePosition = returnLinePosition
  641.  
  642.                 -- capture the return values
  643.                 local values = {}
  644.                 while true do
  645.                     local expression, hasNext = captureReturnParameter()
  646.                     if expression then
  647.                         table.insert(values, expression)
  648.                     end
  649.                     if not expression or not hasNext then
  650.                         break
  651.                     end
  652.                 end
  653.                 if #values > 0 then
  654.                     table.insert(block, values)
  655.                 end
  656.  
  657.                 startLine, startLinePosition = currentLine, currentLinePosition
  658.             elseif BLOCK_START_KEYWORDS[keyword] then
  659.                 level = level + 1
  660.             end
  661.         until level == 0
  662.         return block, blockStartLine
  663.     end
  664.  
  665.     function demandParameters()
  666.  
  667.     end
  668.  
  669.     function stepBack( keyword )
  670.         currentLinePosition = math.max( 1, currentLinePosition - #keyword )
  671.         if currentLinePosition < 1 then
  672.             currentLine = currentLine - 1
  673.         end
  674.     end
  675.  
  676.     function jumpToKeywords( toKeywords )
  677.         local keyword
  678.         local startLine, startLinePosition = currentLine, currentLinePosition
  679.         repeat
  680.             keyword, isEndOfFile = nextName( true )
  681.         until not keyword or toKeywords[keyword]
  682.         if not keyword then
  683.             currentLine, currentLinePosition = #lines, #lines[#lines] + 1
  684.         end
  685.         local jumpedString = ""
  686.         for i = startLine, currentLine do
  687.             local line = lines[i]
  688.             local lineOut = ""
  689.             local lineComments = linesComments[i]
  690.             for n = (i == startLine and startLinePosition or 1), (i == currentLine and currentLinePosition - (keyword and #keyword or 0) - 1 or #line) do
  691.                 if not lineComments or not lineComments[n] then
  692.                     lineOut = lineOut .. line:sub(n, n)
  693.                 end
  694.             end
  695.             jumpedString = jumpedString .. lineOut .. "\n"
  696.         end
  697.         return toKeywords[keyword], keyword, jumpedString, startLine
  698.     end
  699.  
  700.     function isComment( lineNumber, linePosition )
  701.         lineNumber = lineNumber or currentLine
  702.         linePosition = linePosition or currentLinePosition
  703.         return linesComments[lineNumber][linePosition] or false
  704.     end
  705.  
  706.     function captureToKeywords( toKeywords )
  707.         local keyword
  708.         local startLine, startLinePosition = currentLine, currentLinePosition
  709.         repeat
  710.             keyword = nextName()
  711.         until not keyword or toKeywords[keyword]
  712.         stepBack( keyword )
  713.         if startLine ~= currentLine or startLinePosition ~= currentLinePosition then
  714.             local capturedLineStarts = {}
  715.             local capturedLines = {}
  716.             for i = startLine, currentLine do
  717.                 table.insert( capturedLines, i )
  718.                 capturedLineStarts[i] = i == startLine and startLinePosition or ( i == currentLine and currentLinePosition or 1 )
  719.             end
  720.             return capturedLines, capturedLineStarts, startLine, currentLine
  721.         else
  722.             return {}
  723.         end
  724.     end
  725.  
  726.     local fileEnvironment = {
  727.         fileName = fileName
  728.     }
  729.     fileEnvironments[i] = fileEnvironment
  730.     local isInterface = false
  731.     while not isEndOfFile and not ( currentLinePosition > currentLineLength and currentLine + 1 > linesCount ) do
  732.         if blockLevel == BLOCK_LEVEL_NONE then
  733.             -- we're expecting a class definition
  734.             local didJump, keyword, jumpedString, startLine = jumpToKeywords( {[KEYWORD_CLASS] = true, [KEYWORD_INTERFACE] = true}, position, line )
  735.             if not jumpedString:match( "^(%s*)$" ) then
  736.                 -- we only jumped more than just white space or nothing, it might be important
  737.                 local existingLine = fileEnvironment[startLine]
  738.                 if existingLine then
  739.                     -- there might already be other code starting on this line (i.e. the class was on a single line), add to it rather than replacing
  740.                     fileEnvironment[startLine] = existingLine .. " " .. jumpedString
  741.                 else
  742.                     fileEnvironment[startLine] = jumpedString
  743.                 end
  744.             end
  745.             if didJump then
  746.                 local className = demandImmediateNextName( "class name" )
  747.                 if keyword == KEYWORD_CLASS then
  748.                     isInterface = false
  749.                     currentClass = {
  750.                         className = className,
  751.                         instance = {
  752.                             properties = {},
  753.                             propertyMethods = {[KEYWORD_SET] = {},[KEYWORD_DIDSET] = {},[KEYWORD_WILLSET] = {},[KEYWORD_GET] = {}},
  754.                             defaultValues = {},
  755.                             eventHandles = {},
  756.                             functions = {},
  757.                             instanceVariables = {}
  758.                         },
  759.                         static = {
  760.                             properties = {},
  761.                             propertyMethods = {[KEYWORD_SET] = {},[KEYWORD_DIDSET] = {},[KEYWORD_WILLSET] = {},[KEYWORD_GET] = {}},
  762.                             defaultValues = {},
  763.                             eventHandles = {},
  764.                             functions = {},
  765.                             instanceVariables = {}
  766.                         },
  767.                         implements = {},
  768.                         enums = {},
  769.                         fileEnvironment = i,
  770.                         lineNumber = currentLine
  771.                     }
  772.                 elseif keyword == KEYWORD_INTERFACE then
  773.                     isInterface = true
  774.                     currentClass = {
  775.                         isInterface = true,
  776.                         className = className,
  777.                         instance = {
  778.                             properties = {},
  779.                             eventHandles = {},
  780.                             functions = {},
  781.                         },
  782.                         static = {
  783.                             properties = {},
  784.                             eventHandles = {},
  785.                             functions = {},
  786.                         },
  787.                         enums = {}
  788.                     }
  789.                 end
  790.                 if parsed[className] then
  791.                     parserError( "A class/interface has already been defined with the name '" .. className .. "'. Class/interface names must be unique." )
  792.                 end
  793.                 blockLevel = BLOCK_LEVEL_CLASS
  794.                 isClassDefinition = true
  795.             else
  796.                 -- we're not in a class block, we can ignore this line
  797.                 break
  798.             end
  799.  
  800.         elseif blockLevel == BLOCK_LEVEL_CLASS then
  801.             if isClassDefinition then
  802.                 -- we're expecting either the extends, implements, etc, statement OR a property declaration
  803.                 local keyword = immediateNextName()
  804.                 if keyword == KEYWORD_EXTENDS then
  805.                     if isInterface then
  806.                         parserError("attempted to extend interface '" .. currentClass.className .. "'. Interfaces cannot be extended.")
  807.                     end
  808.                     if currentClass.extends then
  809.                         parserError( "class already extends '" .. currentClass.extends .. "'. Polyinheritance is not supported." )
  810.                     end
  811.                     currentClass.extends = demandImmediateNextName()
  812.                 elseif keyword == KEYWORD_IMPLEMENTS then
  813.                     if isInterface then
  814.                         parserError("attempted to implement another interface in interface '" .. currentClass.className .. "'. Interfaces cannot implement other interfaces.")
  815.                     end
  816.                     local implements = demandImmediateNextName()
  817.                     for i, impl in ipairs(currentClass.implements) do
  818.                         if impl == implements then
  819.                             parserError( "duplicate interface implementation, class already implements '" .. impl .. "'." )
  820.                         end
  821.                     end
  822.                     table.insert(currentClass.implements, implements)
  823.                 elseif NON_CLASS_TYPE_DECLARATION_KEYWORDS[keyword] then
  824.                     isClassDefinition = false
  825.                     stepBack( keyword )
  826.                 else
  827.                     parserError( "unexpected keyword '" .. tostring(keyword) .. "', expected class type declaration (extends, implements, etc.) or property/function declaration." )
  828.                 end
  829.             else
  830.                 local keyword = demandNextName( "property or function declaration or 'end'" )
  831.                 if isStatic and not STATIC_KEYWORDS[keyword] then
  832.                     parserError( "invalid keyword '" .. keyword .. "' after 'static'" )
  833.                 end
  834.                 if keyword == KEYWORD_END then
  835.                     blockLevel = BLOCK_LEVEL_NONE
  836.                     parsed[currentClass.className] = currentClass
  837.                 elseif keyword == KEYWORD_STATIC then
  838.                     isStatic = true
  839.                 elseif keyword == KEYWORD_LOCAL then
  840.                     if isInterface then
  841.                         parserError("interfaces cannot define instance variables.")
  842.                     end
  843.                     local name = demandImmediateNextName( "instance variable name" )
  844.  
  845.                     if isStatic then
  846.                         if currentClass.static.instanceVariables[name] then
  847.                             parserError( "duplicate static instance variable for name '" .. name .. "'" )
  848.                         end
  849.                     else
  850.                         if currentClass.instance.instanceVariables[name] then
  851.                             parserError( "duplicate instance variable for name '" .. name .. "'" )
  852.                         end
  853.                     end
  854.  
  855.                     -- try and get the default value out of it
  856.                     local hasEquals = isNext( "^(%s*)=(%s*)", true )
  857.                     local defaultValue
  858.                     if hasEquals then
  859.                         defaultValue = demandFirstExpression( DEFAULT_DELIMITERS, "default value expression" )
  860.                     end
  861.  
  862.                     if isStatic then
  863.                         currentClass.static.instanceVariables[name] = defaultValue
  864.                     else
  865.                         currentClass.instance.instanceVariables[name] = defaultValue
  866.                     end
  867.                 elseif keyword == KEYWORD_ENUM then
  868.                     if isInterface then
  869.                         parserError("interfaces cannot define enums.")
  870.                     end
  871.                     local propertyType = demandNextType( "enum type" )
  872.                     local name = demandImmediateNextName( "enum name" )
  873.  
  874.                     if currentClass.enums[name] then
  875.                         parserError( "duplicate enum for name '" .. name .. "'" )
  876.                     end
  877.  
  878.                     -- try and get the default value out of it
  879.                     local hasEquals = isNext( "^(%s*)=(%s*)", true )
  880.                     local values
  881.                     if hasEquals then
  882.                         values = demandFirstExpression( DEFAULT_DELIMITERS, "default value expression" )
  883.                     else
  884.                         parserError( "expected table of enum values" )
  885.                     end
  886.  
  887.                     currentClass.enums[name] = {
  888.                         type = propertyType,
  889.                         values = values
  890.                     }
  891.                 elseif keyword == KEYWORD_PROPERTY then
  892.                     local nextKeyword = demandNextType( "property type" )
  893.                     local readOnly, allowsNil, link, name, propertyType = false, false, false
  894.                     while true do
  895.                         if nextKeyword == KEYWORD_READONLY then
  896.                             readOnly = true
  897.                         elseif nextKeyword == KEYWORD_ALLOWSNIL then
  898.                             allowsNil = true
  899.                         elseif nextKeyword == KEYWORD_LINK then
  900.                             link = true
  901.                         elseif not nextKeyword then
  902.                             -- no type
  903.                             name = propertyType
  904.                             propertyType = nil
  905.                             if allowsNil then
  906.                                 error("compiler error: unnecessary allowsNil modifier on property '" .. name .. "' in class '" .. currentClass.className .. "'. Properties without a specified type automatically allow nil values.")
  907.                             end
  908.                             allowsNil = true
  909.                             break
  910.                         elseif not propertyType then
  911.                             propertyType = nextKeyword
  912.                         else
  913.                             name = nextKeyword
  914.                             break
  915.                         end
  916.                         nextKeyword = immediateNextName( "property type modifiers (readOnly, allowsNil, etc.) or property name" )
  917.                     end
  918.  
  919.                     if isStatic then
  920.                         if currentClass.static.properties[name] then
  921.                             parserError( "duplicate static property for name '" .. name .. "'" )
  922.                         end
  923.                     else
  924.                         if currentClass.instance.properties[name] then
  925.                             parserError( "duplicate property for name '" .. name .. "'" )
  926.                         end
  927.                     end
  928.  
  929.                     -- try and get the default value out of it
  930.                     local hasEquals = isNext( "^(%s*)=(%s*)", true )
  931.                     local defaultValue
  932.                     if hasEquals then
  933.                         if isInterface then
  934.                             parserError("interfaces cannot set default property values.")
  935.                         end
  936.                         defaultValue = demandFirstExpression( DEFAULT_DELIMITERS, "default value expression" )
  937.                     end
  938.  
  939.                     local propertyTable = {
  940.                         type = propertyType,
  941.                         allowsNil = allowsNil,
  942.                         readOnly = readOnly,
  943.                         link = link,
  944.                         defaultValue = defaultValue
  945.                     }
  946.                     if isStatic then
  947.                         currentClass.static.properties[name] = propertyTable
  948.                     else
  949.                         currentClass.instance.properties[name] = propertyTable
  950.                     end
  951.                 elseif keyword == KEYWORD_DEFAULT then
  952.                     if isInterface then
  953.                         parserError("interfaces cannot set default property values.")
  954.                     end
  955.                     local name = demandImmediateNextName( "property name" )
  956.                     local hasEquals = isNext( "^(%s*)=(%s*)", true )
  957.                     if hasEquals then
  958.                         local expression = demandFirstExpression( DEFAULT_DELIMITERS, "default value expression" )
  959.                         if isStatic then
  960.                             currentClass.static.defaultValues[name] = expression
  961.                         else
  962.                             currentClass.instance.defaultValues[name] = expression
  963.                         end
  964.                     else
  965.                         parserError( "default expects equals sign and new default value after property name" )
  966.                     end
  967.                 elseif keyword == KEYWORD_SET or keyword == KEYWORD_DIDSET or keyword == KEYWORD_WILLSET or keyword == KEYWORD_GET then
  968.                     if isInterface then
  969.                         parserError("interfaces cannot require property methods.")
  970.                     end
  971.                     local name = demandImmediateNextName( "property name" )
  972.                     local propertyMethodTable
  973.                     if isStatic then
  974.                         propertyMethodTable = currentClass.static.propertyMethods[keyword]
  975.                     else
  976.                         propertyMethodTable = currentClass.instance.propertyMethods[keyword]
  977.                     end
  978.                     if propertyMethodTable[name] then
  979.                         parserError( "Attempted to redefine '" .. keyword .. "' for property '" .. name .. "'." )
  980.                     end
  981.                     captureSingleParameter( keyword == KEYWORD_GET and "" or name )
  982.                    
  983.                     local block, startLine = captureBlock()
  984.                     propertyMethodTable[name] = { "(__CLASS__self_passed" .. ( keyword == KEYWORD_GET and "" or ( "," .. name ) ) .. ")" .. block, startLine }
  985.                 elseif keyword == KEYWORD_EVENT then
  986.                     local name = demandImmediateNextName( "event function handle name" )
  987.                     local eventClass, eventPhase, parameterName = captureEventParameter()
  988.                     local eventTable
  989.                     if not isInterface then
  990.                         local block, startLine = captureBlock()
  991.                         eventTable = { "(__CLASS__self_passed," .. parameterName ..")", block, startLine, eventClass, eventPhase }
  992.                     else
  993.                         eventTable = { eventClass, eventPhase }
  994.                     end
  995.                     if isStatic then
  996.                         currentClass.static.eventHandles[name] = eventTable
  997.                     else
  998.                         currentClass.instance.eventHandles[name] = eventTable
  999.                     end
  1000.                 elseif keyword == KEYWORD_FUNCTION then
  1001.                     local returnTypes = {}
  1002.                     local currentReturnType
  1003.                     local hasNext = false
  1004.                     local name
  1005.                     -- capture the return types
  1006.                     repeat
  1007.                         local nextKeyword = nextType( "return type modifiers (readOnly, allowsNil, etc.) or function name" )
  1008.                         if not nextKeyword then
  1009.                             -- no return type was given, hence we allow ANY return value and any amount of return values. If you don't want to return anything use nil
  1010.                             if currentReturnType then
  1011.                                 -- the current return type is actually the function name, we don't have a type
  1012.                                 name = currentReturnType.type
  1013.                                 returnTypes = nil
  1014.                                 break
  1015.                             else
  1016.                                 -- no function name was given
  1017.                                 parserError("expected function name")
  1018.                             end
  1019.                         elseif nextKeyword == KEYWORD_ALLOWSNIL then
  1020.                             if not currentReturnType then
  1021.                                 parserError( "expected return type before 'allowsNil' modifier" )
  1022.                             end
  1023.                             currentReturnType.allowsNil = true
  1024.                             -- we can't modify the property any further, add the return type to the stack
  1025.                             table.insert( returnTypes, currentReturnType )
  1026.                             currentReturnType = nil
  1027.                         elseif currentReturnType then
  1028.                             -- nextKeyword is actually the function name, add the return type to the stack
  1029.                             name = nextKeyword
  1030.                             table.insert( returnTypes, currentReturnType )
  1031.                             currentReturnType = nil
  1032.                         else
  1033.                             currentReturnType = { type = nextKeyword, allowsNil = false }
  1034.                         end
  1035.  
  1036.                         hasNext = isNext( "^%s*%,", true )
  1037.                         if hasNext and name then
  1038.                             parserError( "invalid return type modifier '" .. name .. "'" )
  1039.                         end
  1040.                         if hasNext then
  1041.                             table.insert( returnTypes, currentReturnType )
  1042.                             currentReturnType = nil
  1043.                             -- varargs might be next (which won't be captured as a keyword)
  1044.                             local isVarArg = isNext( "^%s*%.%.%.%s*", true )
  1045.                             if isVarArg then
  1046.                                 table.insert( returnTypes, { isVarArg = true } )
  1047.                                 hasNext = false
  1048.                             end
  1049.                         end
  1050.                     until not currentReturnType and not hasNext
  1051.  
  1052.                     if not name then
  1053.                         name = demandImmediateNextName( "event function handle name" )
  1054.                     end
  1055.  
  1056.                     local functionTable = { false --[[ simply a placeholder to be once the block can be captured ]] }
  1057.  
  1058.                     -- capture the parameters
  1059.                     local parameters = {}
  1060.                     hasOpeningBracket = isNext( "^%s*%(%s*", true )
  1061.                     if not hasOpeningBracket then
  1062.                         parserError( "expected opening bracket for function parameters" )
  1063.                     end
  1064.                     repeat
  1065.                         local allowsNil, parameterName = false
  1066.                         -- first we try to capture the type, however, this may also be the name. this is needed because the type isn't always a keyword (for tabes and dictionary types)
  1067.                         local parameterType, parameterIsTable = nextType()
  1068.                         if not parameterType then
  1069.                             -- there wasn't a type/name given, try to capture varargs (...)
  1070.                             local isVarArg = isNext( "^%s*%.%.%.%s*%)", true )
  1071.                             if isVarArg then
  1072.                                 table.insert(parameters, {
  1073.                                     isVarArg = true,
  1074.                                     name = "..."
  1075.                                 })
  1076.                                 stepBack(")")
  1077.                             end
  1078.                             break
  1079.                         end
  1080.                         while true do
  1081.                             local nextKeyword = immediateNextName()
  1082.                             if not nextKeyword then
  1083.                                 if parameterType then
  1084.                                     if parameterIsTable then
  1085.                                         parserError( "expected modifier (" .. KEYWORD_ALLOWSNIL .. ") or property name" )
  1086.                                     else
  1087.                                         -- a type wasn't given, we'll allow anything (including nil)
  1088.                                         parameterName = parameterType
  1089.                                         parameterType = nil
  1090.                                         allowsNil = true
  1091.                                         break
  1092.                                     end
  1093.                                 else
  1094.                                     parserError( "expected parameter type, modifier (" .. KEYWORD_ALLOWSNIL .. ") or property name" )
  1095.                                 end
  1096.                             elseif nextKeyword == KEYWORD_ALLOWSNIL then
  1097.                                 if not parameterType then
  1098.                                     parserError( "expected parameter type before modifier '" .. nextKeyword .. "'" )
  1099.                                 end
  1100.                                 allowsNil = true
  1101.                             elseif not parameterType then
  1102.                                 parameterType = nextKeyword
  1103.                             else
  1104.                                 parameterName = nextKeyword
  1105.                                 break
  1106.                             end
  1107.                         end
  1108.                         if parameterName == "self" or parameterName == "super" then
  1109.                             parserError("attempted to use '" .. parameterName .. "' as a parameter name. " .. parameterName .. " is automatically passed and cannot be used for another parameter.")
  1110.                         end
  1111.                         -- try and get the default value out of it
  1112.                         local hasEquals = isNext( "^(%s*)=(%s*)", true )
  1113.                         local defaultValue
  1114.                         if hasEquals then
  1115.                             defaultValue = captureParameterDefault()
  1116.                         end
  1117.  
  1118.                         table.insert(parameters, {
  1119.                             allowsNil = allowsNil,
  1120.                             type = parameterType,
  1121.                             name = parameterName,
  1122.                             defaultValue = defaultValue
  1123.                         })
  1124.                         hasNext = isNext( "^%s*%,", true )
  1125.                     until not hasNext
  1126.                     isNext( "^%s*%)", true ) -- capture the trailing bracket
  1127.                     local functionTable
  1128.                     if not isInterface then
  1129.                         local block, startLine = captureReturnsBlock()
  1130.                         local functionString = "(__CLASS__self_passed"
  1131.                         local parameterNames = {}
  1132.                         for i, parameter in ipairs( parameters ) do
  1133.                             functionString = functionString  .. "," .. parameter.name
  1134.                             table.insert(parameterNames, parameter.name)
  1135.                         end
  1136.                         functionTable = { functionString .. ")", block, startLine, parameters, returnTypes, parameterNames }
  1137.                     else
  1138.                         functionTable = { parameters, returnTypes }
  1139.                     end
  1140.                     if isStatic then
  1141.                         currentClass.static.functions[name] = functionTable
  1142.                     else
  1143.                         currentClass.instance.functions[name] = functionTable
  1144.                     end
  1145.                 else
  1146.                     -- break
  1147.                     parserError( "unexpected keyword '" .. tostring(keyword) .. "', expected property/function declaration or 'end'." )
  1148.                 end
  1149.  
  1150.                 if isStatic and keyword ~= KEYWORD_STATIC then
  1151.                     isStatic = false
  1152.                 end
  1153.             end
  1154.         end
  1155.     end
  1156.     print( "Parsed '" .. fileName .. "' without error in " .. os.clock() - fileStartTime .. "s!" )
  1157. end
  1158.  
  1159. print("Parsed in " .. os.clock() - parseStartTime .. "s!")
  1160.  
  1161. -- END PARSER
  1162.  
  1163. -- BEGIN COMPILER
  1164.  
  1165. local compileStartTime = os.clock()
  1166. print("Starting compile...")
  1167. local file = ""
  1168.  
  1169. local function add(str, ...)
  1170.     file = file .. string.format(str, ...)
  1171. end
  1172.  
  1173. -- escape square brackets
  1174. local function escapeSquare(str)
  1175.     return str:gsub("%[(=*)%[", "[%1=["):gsub("%](=*)%]", "]%1=]")
  1176. end
  1177.  
  1178. local idN = 1
  1179. local idPrefix = "__CLASS__"
  1180. local idChars = {"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"}
  1181. local idCharsLength = #idChars
  1182. local function nextID()
  1183.     local id = ""
  1184.     local rem = idN
  1185.     while rem > 0 do
  1186.         id = idChars[(rem - 1) % idCharsLength + 1] .. id
  1187.         rem = floor((rem - 1) / idCharsLength)
  1188.     end
  1189.     idN = idN + 1
  1190.     return idPrefix .. id
  1191. end
  1192.  
  1193. local function addFunction(str, ...)
  1194.     local functionID = nextID()
  1195.     add("local function %s%s ", functionID, string.format(str, ...))
  1196.     return functionID
  1197. end
  1198.  
  1199.  
  1200. local lineNumberMap = {}
  1201. local lineNumberingOutputLine, lineNumberingSourceFile, lineNumberingSourceLine
  1202. -- add a line number anchor
  1203. local function startLineNumbering(sourceFile, sourceLine)
  1204.     sourceFile = fileEnvironments[sourceFile].fileName
  1205.     local outputLine = 1
  1206.     for i in string.gmatch(file, "\n") do
  1207.        outputLine = outputLine + 1
  1208.     end
  1209.     if lineNumberMap[outputLine] then
  1210.         error("duplicate error line on " .. outputLine)
  1211.     else
  1212.         lineNumberingOutputLine, lineNumberingSourceFile, lineNumberingSourceLine = outputLine, sourceFile, sourceLine
  1213.     end
  1214. end
  1215.  
  1216. -- fill from the first line number anchor up to where we are now
  1217. local function stopLineNumbering()
  1218.     local outputLine = 1
  1219.     for i in string.gmatch(file, "\n") do
  1220.        outputLine = outputLine + 1
  1221.     end
  1222.     add("\n")
  1223.     for i = 0, outputLine - lineNumberingOutputLine do
  1224.         lineNumberMap[lineNumberingOutputLine + i] = lineNumberingSourceFile .. ":" .. lineNumberingSourceLine + i .. ":"
  1225.     end
  1226.     lineNumberingOutputLine, lineNumberingSourceFile, lineNumberingSourceLine = nil, nil, nil
  1227. end
  1228.  
  1229. -- first add all the built in functions
  1230. local functionErrorID = addFunction([[(m)error(m,3)end]])
  1231.  
  1232. local functionErrorIDs = {
  1233.     metatable = {
  1234.         class = {
  1235.             index = addFunction([[(_,k)%s("attempted to access non-existant enum '"..k.."' of class '"..tostring(_).."'")end]], functionErrorID),
  1236.             newindex = addFunction([[(_,k,v)%s("attempted to mutate class '"..tostring(_).."' using key '"..k.."'")end]], functionErrorID)
  1237.         },
  1238.         interface = {
  1239.             index = addFunction([[(_)%s("attempted to access value from interface '"..tostring(_).."'")end]], functionErrorID),
  1240.             newindex = addFunction([[(_,k)%s("attempted to mutate interface '"..tostring(_).."' using key '"..k.."'")end]], functionErrorID)
  1241.         },
  1242.         instance = {
  1243.             index = addFunction([[(_,k)%s("attempted to access non-existant property '"..k.."' of '"..tostring(_).."'")end]], functionErrorID),
  1244.             newindex = addFunction([[(_,k,v)%s("attempted to set non-existant property '"..k.."' of '"..tostring(_).."' to '"..v.."'")end]], functionErrorID)
  1245.         },
  1246.         func = {
  1247.             newindex = addFunction([[(_,k,v)%s("attempted to set value of function '"..k.."' of '"..tostring(_).."' to '"..v.."'")end]], functionErrorID)
  1248.         },
  1249.         enum = {
  1250.             index = addFunction([[(_,k)%s("attempted to access non-existant key '"..k.."' from enum '"..tostring(_).."'")end]], functionErrorID),
  1251.             newindex = addFunction([[(_,k,v)%s("attempted to mutate enum '"..tostring(_).."' key '"..tostring(k).."' to '"..v.."'")end]], functionErrorID)
  1252.         },
  1253.         super = {
  1254.             index = addFunction([[(_,k)if k=="super"then %s("tried to access super of '" .. tostring(_) .. "', which does not have a super")else %s("attempted to access invalid index '" .. k .. "' of super '" .. tostring(_) .. "'")end end]], functionErrorID, functionErrorID),
  1255.             newindex = addFunction([[(_,k,v)%s("attempted to mutate super '"..tostring(_).."' using key '"..k.."'")end]], functionErrorID)
  1256.         },
  1257.     },
  1258.     readOnly = addFunction([[(_,k,v)%s("attempted to set read only property '"..k.."' of '"..tostring(_).."' to '"..v.."'")end]], functionErrorID),
  1259.     type = {
  1260.         property = addFunction([[(_,k,t,v)%s("attempted to set property '"..k.."' of '"..tostring(_).."' to an invalid value '"..tostring(v).."', expected type '"..t.."'")end]], functionErrorID),
  1261.         default = addFunction([[(_,k,t,v)%s("default value of property '"..k.."' of '"..tostring(_).."' was an invalid value '"..tostring(v).."', expected type '"..t.."'")end]], functionErrorID),
  1262.         parameter = addFunction([[(_,k,f,t,v)%s("attempted to pass parameter '"..k.."' of '"..tostring(_).."."..f.."' an invalid value '"..tostring(v).."', expected type '"..t.."'")end]], functionErrorID),
  1263.         enum = addFunction([[(_,k,t,v)%s("enum '".._.."' has an invalid value '"..tostring(v).."' for key '"..k.."', expected type '"..t.."'")end]], functionErrorID),
  1264.         returned = {
  1265.             get = addFunction([[(_,k,t,v)%s("return value from getter '"..k.."' of '"..tostring(_).."' was an invalid value '"..tostring(v).."', expected type '"..t.."'")end]], functionErrorID),
  1266.             func = addFunction([[(_,k,i,v,t)%s("return value "..i.." from function '"..k.."' of '"..tostring(_).."' was an invalid value '"..tostring(v).."', expected type '"..t.."'")end]], functionErrorID),
  1267.         }
  1268.     }
  1269. }
  1270. local functionExecuteID = addFunction([[(f,e,...)return setfenv(f, e)(...)end]])
  1271.  
  1272. -- TODO: minify when it 100% works
  1273. local functionErrorProxyID = addFunction([[(name, startLine, func)
  1274.     return select(2, xpcall(func,  function(err)
  1275.         local _, trace = pcall(error, "<@", 3)
  1276.         local lineNumber = trace:match(":(%%d+): <@")
  1277.         if not lineNumber then print(err)
  1278.         else
  1279.             print(name .. ":" .. lineNumber + startLine .. ": " .. err:match(":" .. lineNumber .. ": (.*)$"))
  1280.         end
  1281.     end))
  1282. end]])
  1283.  
  1284. -- create tables for instances. every instance gets added to this table (but it is weak to prevent leaks). this is used for checking if a table is an instance
  1285. local instancesID = nextID()
  1286. add("local %s=setmetatable({},{__mode=\"k\"})", instancesID)
  1287. local staticInstancesID = nextID()
  1288. add("local %s=setmetatable({},{__mode=\"k\"})", staticInstancesID)
  1289.  
  1290. -- add the type of function
  1291. add("function typeOf(i,t)local j=%s[i]if j then return j[t] and true or false else local s=%s[i]if s then return s[t] and true or false end return false end end ", instancesID, staticInstancesID)
  1292.  
  1293. local classNamesIDs = {}
  1294. for className, _ in pairs(parsed) do
  1295.     local id = nextID()
  1296.     classNamesIDs[className] = id
  1297.     add("local %s=%q", id, className)
  1298. end
  1299.  
  1300. local instanceCreateID, localiseFunctionsID = nextID(), nextID()
  1301.  
  1302. add([[local function %s(instance, funcs, instanceEnvironment, supers, supers__tostrings)
  1303.     local isFirst = true
  1304.     local superFuncs = {}
  1305.     local stack = {}
  1306.     local n = 1
  1307.     local f
  1308.     for i = 1, #supers + 1 do
  1309.         local func = funcs[i]
  1310.         if func then
  1311.             if not isFirst then
  1312.                 stack[n] = {i, func}
  1313.                 n = n + 1
  1314.             else
  1315.                 f = func
  1316.                 isFirst = false
  1317.             end
  1318.         end
  1319.     end
  1320.     for i = n - 1, 1, -1 do
  1321.         local details = stack[i]
  1322.         local superI = details[1]
  1323.         local func = setfenv(details[2](supers[superI], superFuncs[i + 1]), instanceEnvironment)
  1324.         local super = superFuncs[i + 1]
  1325.         local __index
  1326.         if super then
  1327.             __index = function(_, k, v)
  1328.                 if k == "super" then
  1329.                     return super
  1330.                 else
  1331.                     %s(_, k, v) -- __CLASS__error_function_super_no_index
  1332.                 end
  1333.             end
  1334.         else
  1335.             __index = %s
  1336.         end
  1337.         superFuncs[i] = setmetatable({}, {__index = __index, __newindex = __CLASS__8, __tostring = supers__tostrings[superI], __call = function(_, ...)return func(super, ...) end})
  1338.     end
  1339.     return setfenv(f(instance, superFuncs[1]), instanceEnvironment)
  1340. end
  1341.  
  1342. local function %s(instance, instanceType, instanceVariables, instanceFunctions, defaultValues, defaultTypes, environmentClass, name, instance__index, instance__newindex, supers_names, propertyMethodFunctions, typeOfTree, ...)
  1343.     (instanceType == "static" and %s or %s)[instance] = typeOfTree -- instancesID/staticInstancesID
  1344.     local instanceEnvironment
  1345.     if instanceVariables then
  1346.         instanceEnvironment = setmetatable({}, {
  1347.             __index = function(_, k)
  1348.                 if not instanceVariables[k] then -- if an instance variable is nil and there is an upvalue local with the same name the instance variable will hide the upvalue, even when nil
  1349.                     return environmentClass[k]
  1350.                 end
  1351.             end,
  1352.             __newindex = function(_, k, v)
  1353.                 if not instanceVariables[k] then -- all instance variables hide upvalues, regardless of their value
  1354.                     environmentClass[k] = v -- TODO: should this be the class environment, or _G?
  1355.                 else
  1356.                     rawset(_, k, v)
  1357.                 end
  1358.             end
  1359.         })
  1360.         for k, v in pairs(instanceVariables) do
  1361.             instanceEnvironment[k] = %s(v, environmentClass)
  1362.         end
  1363.     else
  1364.         instanceEnvironment = environmentClass
  1365.     end
  1366.  
  1367.     local values = {}
  1368.     local getLocks, setLocks = {}, {}
  1369.     local __tostring = instanceType .. " of '" .. name .. "': " .. tostring(instance):sub(8)
  1370.     local getMethods, willSetMethods, setMethods, didSetMethods = {}, {}, {}, {}
  1371.     setmetatable(instance, {
  1372.         __index = function(_,k)
  1373.             return instance__index[k](instance, k, values, getMethods, getLocks)
  1374.         end,
  1375.         __newindex = function(_,k,v)
  1376.             instance__newindex[k](instance, k, v, values, willSetMethods, setMethods, didSetMethods, setLocks)
  1377.         end,
  1378.         __tostring = function() return __tostring end
  1379.     })
  1380.  
  1381.     if defaultValues then
  1382.         for k,v in pairs(defaultValues) do
  1383.             local newValue = %s(v, environmentClass)
  1384.             local neededType = defaultTypes[k]
  1385.             if neededType and neededType[1](newValue) then
  1386.                 %s(instance,k,neededType[2],newValue)
  1387.             end
  1388.             values[k] = newValue
  1389.         end
  1390.     end
  1391.  
  1392.     local supers = {}
  1393.     local supers__tostrings = {}
  1394.     local supersEnvironments = {}
  1395.     local supersValues = {}
  1396.     if supers_names then
  1397.         for i, super_name in ipairs(supers_names) do
  1398.             local super = {}
  1399.             -- local superValues = setmetatable({}, {__index = values, __newindex = function(_,k,v)values[k] = v end})
  1400.             local super__tostring = "super '" .. super_name .. "': " .. tostring(super):sub(8) .. " of " .. __tostring
  1401.             local super__tostring_func = function() return super__tostring end
  1402.             setmetatable(super, {
  1403.                 __index = function(_,k)
  1404.                     return instance__index[k](super, k, values, instanceEnvironment, getLocks)
  1405.                 end,
  1406.                 __newindex = function(_,k,v)
  1407.                     instance__newindex[k](super, k, v, values, instanceEnvironment, setLocks)
  1408.                 end,
  1409.                 __tostring = super__tostring_func
  1410.             })
  1411.             supers[i +1] = super
  1412.             supers__tostrings[i +1] = super__tostring_func
  1413.         end
  1414.     end
  1415.     if propertyMethodFunctions then
  1416.         -- getMethods, willSetMethods, setMethods, didSetMethods = {}, {}, {}, {}
  1417.         local propertyMethods = {get = getMethods, willSet = willSetMethods, set = setMethods, didSet = didSetMethods}
  1418.         for methodName, properties in pairs(propertyMethodFunctions) do
  1419.             for propertyName, funcs in pairs(properties) do
  1420.                 propertyMethods[methodName][propertyName] = %s(instance, funcs, instanceEnvironment, supers, supers__tostrings)
  1421.             end
  1422.         end
  1423.     end
  1424.  
  1425.  
  1426.     if instanceFunctions then
  1427.         for k, funcs in pairs(instanceFunctions) do
  1428.             values[k] = %s(instance, funcs, instanceEnvironment, supers, supers__tostrings)
  1429.         end
  1430.     end
  1431.  
  1432.     values.typeOf = function(_,other)
  1433.         return typeOfTree[other] == 1
  1434.     end
  1435.  
  1436.     local initialiseFunction = values.initialise
  1437.     if initialiseFunction then
  1438.         initialiseFunction(instance,...)
  1439.     end
  1440.     return instance
  1441. end ]], localiseFunctionsID, functionErrorIDs.metatable.super.newindex, functionErrorIDs.metatable.super.newindex, instanceCreateID, instancesID, staticInstancesID, functionExecuteID, functionExecuteID, functionErrorIDs.type.default, localiseFunctionsID, localiseFunctionsID)
  1442.  
  1443. -- now add the file environments
  1444. local fileEnvironmentIDs = {}
  1445. for i, blocks in ipairs(fileEnvironments) do
  1446.     local id = nextID()
  1447.     fileEnvironmentIDs[i] = id
  1448.     add("local %s=setmetatable({},{__index=_G})", id)
  1449. end
  1450.  
  1451. -- figure out the order that the classes need to be loaded (supers first)
  1452. local classOrder = {}
  1453. local orderedClasses = {}
  1454. local function addClassOrder(className, classDetails)
  1455.     -- don't add a class that's already loaded
  1456.     if not orderedClasses[className] then
  1457.         -- if the class has a super we need to place this AFTER the super
  1458.         if classDetails.extends then
  1459.             if not orderedClasses[classDetails.extends] then
  1460.                 -- the super hasn't been added yet, do that now
  1461.                 addClassOrder(classDetails.extends, parsed[classDetails.extends])
  1462.             end
  1463.         end
  1464.         -- the super has been added to the list or there isn't one, we can simply add ourself to the end
  1465.         insert(classOrder, classDetails)
  1466.         orderedClasses[className] = true
  1467.     end
  1468. end
  1469. for className, classDetails in pairs(parsed) do
  1470.     addClassOrder(className, classDetails)
  1471. end
  1472.  
  1473. -- now create empty tables for all the classes and their statics (which are filled later on) so we can reference them directly when needed
  1474. local classStaticIDs = {}
  1475. local classTostringIDs = {}
  1476. local classesString = "local %s={"
  1477. local didAdd = false
  1478. for className, classDetails in pairs(parsed) do
  1479.     -- TODO: maybe add an option which makes these locals?
  1480.     didAdd = true
  1481.     classesString = classesString .. string.format("[%s]=true,", className)
  1482.     add("%s={}", className)
  1483.     local tostringID = nextID()
  1484.     add("local %s=\"" .. (classDetails.isInterface and "interface" or "class") .. " '%s': \"..tostring(%s):sub(8)", tostringID, className, className)
  1485.     classTostringIDs[className] = tostringID
  1486.     if not classDetails.isInterface then
  1487.         local staticID = nextID()
  1488.         classStaticIDs[className] = staticID
  1489.         add("local %s={}", staticID)
  1490.     end
  1491. end
  1492. if didAdd then
  1493.     classesString = classesString:sub(1, #classesString - 1)
  1494. end
  1495. local classesID = nextID()
  1496. add(classesString .. "}", classesID)
  1497.  
  1498. local classesEnumIDs = {}
  1499. local classesEnumTypeOfTableIDs = {}
  1500. local functionTypeCheckIDs = {
  1501.     [true] = {}, [false] = {}
  1502. }
  1503. local standardTypes = {
  1504.     String = true,
  1505.     Number = true,
  1506.     Boolean = true,
  1507.     Table = true,
  1508.     Thread = true,
  1509.     Function = true
  1510. }
  1511. local TYPE_ANY = "Any"
  1512. local function typeCheck(typeName, allowsNil)
  1513.     if allowsNil == nil then error("missing allowsNil value") end
  1514.     -- allowsNil = allowsNil ~= nil and allowsNil or false
  1515.     if not typeName or (typeName == TYPE_ANY and allowsNil) then
  1516.         return -- Any allowsNil doesn't do any type checking
  1517.     end
  1518.     local ids = functionTypeCheckIDs[allowsNil]
  1519.     if ids[typeName] then
  1520.         return ids[typeName]
  1521.     end
  1522.  
  1523.     local id
  1524.     local allowsNilCheckOr = allowsNil and "v~=nil and " or ""
  1525.     local allowsNilCheckReturnIf = allowsNil and "if v==nil then return false end " or ""
  1526.     local allowsNilCheckReturnIfNotTable = allowsNil and "if v==nil then return false elseif type(v)~=\"table\" then return true end " or "if type(v)~=\"table\" then return true end "
  1527.     if typeName == TYPE_ANY then
  1528.         id = addFunction("(v)return v~=nil end")
  1529.     elseif standardTypes[typeName] then
  1530.         id = addFunction("(v)return %stype(v)~=\"%s\"end", allowsNilCheckOr, typeName:lower())
  1531.     elseif typeName == "Class" then -- this means a CLASS, not an instance, not a static instance, a plain old class
  1532.         id = addFunction("(v)return %snot %s[v]or true end", allowsNilCheckOr, classesID)
  1533.     elseif typeName:match("^{.+}$") then
  1534.         local dictionaryTypeName1, dictionaryTypeName2 = typeName:match("^{([_%a][_%w]*)=([_%a][_%w]*)}$")
  1535.         if dictionaryTypeName1 and dictionaryTypeName2 then
  1536.             id = addFunction("(v)%sfor k,V in pairs(v)do if %s(k)or %s(V)then return true end end return false end", allowsNilCheckReturnIfNotTable, typeCheck(dictionaryTypeName1, false), typeCheck(dictionaryTypeName2, false))
  1537.         else
  1538.             local arrayTypeName = typeName:match("^{([_%a][_%w]*)}$")
  1539.             if arrayTypeName then
  1540.                 id = addFunction("(v)%sfor i,V in pairs(v)do if %s(k)then return true end end return false end", allowsNilCheckReturnIfNotTable, typeCheck(arrayTypeName, false))
  1541.             end
  1542.         end
  1543.     else
  1544.         local staticClassName = typeName:match("^([_%a][_%w]*)%.static$")
  1545.         if staticClassName then
  1546.             -- this is a static class
  1547.             -- first check if that class name is defined
  1548.             if not standardTypes[staticClassName] and parsed[staticClassName] then
  1549.                 id = addFunction("(v)return %snot %s[v][%s]end", allowsNilCheckOr, staticInstancesID, staticClassName)
  1550.             else
  1551.                 error("compiler error: unknown class type '" .. typeName .. "' (class isn't defined)")
  1552.             end
  1553.         else
  1554.             local className, enumName = typeName:match("^([_%a][_%w]*)%.([_%a][_%w]*)$")
  1555.             if className and enumName then
  1556.                 -- this is an enum
  1557.                 local classEnumIDs = classesEnumIDs[className]
  1558.                 local classEnumTypeOfTableIDs = classesEnumTypeOfTableIDs[className]
  1559.                 if classEnumIDs then
  1560.                     local enumID = classEnumIDs[enumName]
  1561.                     local typeOfTableID = classEnumTypeOfTableIDs[enumName]
  1562.                     id = addFunction("(v)return %snot %s[v] end", allowsNilCheckOr, typeOfTableID)
  1563.                 else
  1564.                 end
  1565.             else
  1566.             end
  1567.         end
  1568.     end
  1569.     if not id then
  1570.         error("compiler error: unknown type '" .. typeName .. "'")
  1571.     end    
  1572.     ids[typeName] = id
  1573.     return id
  1574. end
  1575.  
  1576. -- the blank __index and __newindex functions for properties
  1577. local functionBlankIndexID = nextID()
  1578. add("local function %s(_,k,v)return v[k] end ", functionBlankIndexID)
  1579. local functionBlankNewindexIDs = {[true]={},[false]={}}
  1580. local function blankNewindexIDs(typeName, allowsNil)
  1581.     if not typeName then typeName = TYPE_ANY allowsNil = true end
  1582.     if functionBlankNewindexIDs[allowsNil][typeName] then
  1583.         return functionBlankNewindexIDs[allowsNil][typeName]
  1584.     else
  1585.         local id = nextID()
  1586.         functionBlankNewindexIDs[allowsNil][typeName] = id
  1587.         if typeName == TYPE_ANY then
  1588.             if allowsNil then
  1589.                 add("local function %s(_,k,n,v)v[k]=n end ", id)
  1590.             else
  1591.                 add("local function %s(_,k,n,v)if n==nil then %s(_,k,%q,n)end v[k]=n end ", id, functionErrorIDs.type.property, typeName)
  1592.             end
  1593.         else
  1594.             add("local function %s(_,k,n,v)if %s(n)then %s(_,k,%q,n)end v[k]=n end ", id, typeCheck(typeName, allowsNil), functionErrorIDs.type.property, typeName .. (allowsNil and " (allowsNil)" or ""))
  1595.         end
  1596.         return id
  1597.     end
  1598. end
  1599.  
  1600. local classMetatableMetatableID = nextID()
  1601. add("local %s={__index=%s,__newindex=%s}", classMetatableMetatableID, functionErrorIDs.metatable.class.index, functionErrorIDs.metatable.class.newindex)
  1602. local instanceMetatableMetatableID = nextID()
  1603. add("local %s={__index=%s,__newindex=%s}", instanceMetatableMetatableID, functionErrorIDs.metatable.instance.index, functionErrorIDs.metatable.instance.newindex)
  1604.  
  1605. -- first create the enums (so they can be used for type checking)
  1606. for i, classDetails in pairs(classOrder) do
  1607.     if not classDetails.isInterface then
  1608.         local className = classDetails.className
  1609.         -- create the enums
  1610.         local enumIDs = {}
  1611.  
  1612.         local enumTypeOfTableIDs = {}
  1613.         for enumName, content in pairs(classDetails.enums) do
  1614.             -- create enum's value table ids
  1615.             local enumID = nextID()
  1616.             enumIDs[enumName] = enumID
  1617.             add("local %s ", enumID)
  1618.             local typeOfTableID = nextID()
  1619.             add("local %s={}", typeOfTableID)
  1620.             enumTypeOfTableIDs[enumName] = typeOfTableID
  1621.         end
  1622.         classesEnumIDs[className] = enumIDs
  1623.         classesEnumTypeOfTableIDs[className] = enumTypeOfTableIDs
  1624.     end
  1625. end
  1626.  
  1627. -- now create each class one by one
  1628. local metatableStrings = {}
  1629. local valueTableStrings = {}
  1630. local staticInitialiseStrings = {}
  1631. for i, classDetails in pairs(classOrder) do
  1632.     local className = classDetails.className
  1633.     print("Compiling: " .. className)
  1634.     if not classDetails.isInterface then
  1635.         local interfaces = {}
  1636.         for i, interfaceName in ipairs(classDetails.implements) do
  1637.             table.insert(interfaces, parsed[interfaceName])
  1638.         end
  1639.  
  1640.         -- create the tree of super details
  1641.         local supersDetails = {classDetails}
  1642.         local nextSuperName = classDetails.extends
  1643.         local previousSuperName = className
  1644.         while nextSuperName do
  1645.             local superDetails = parsed[nextSuperName]
  1646.             if not superDetails then
  1647.                 error("compile error: unable to find class '" .. nextSuperName .. "' to extend class '" .. previousSuperName .. "' from")
  1648.             end
  1649.             table.insert(supersDetails, superDetails)
  1650.             previousSuperName = nextSuperName
  1651.             nextSuperName = superDetails.extends
  1652.         end
  1653.  
  1654.         -- create the enums
  1655.         local enumIDs = classesEnumIDs[className]
  1656.         -- add the class' value table
  1657.         local valueTableId = nextID()
  1658.         local valueTableString = "local %s=setmetatable({static=%s"
  1659.         for enumName, id in pairs(enumIDs) do
  1660.             valueTableString = valueTableString .. string.format(",%s=%s", enumName, id)
  1661.         end
  1662.         valueTableStrings[className] = {valueTableString .. "},%s)", valueTableId, classStaticIDs[className], classMetatableMetatableID}
  1663.  
  1664.         -- setup the instance and static values and functions
  1665.         for mode, modeDetails in pairs({instance = classDetails.instance, static = classDetails.static}) do
  1666.             -- create the detault values
  1667.             local defaultValuesID = nextID()
  1668.             local defaultValuesTypesID = nextID()
  1669.             -- we need to collate all the places where default values are set and flatten it in to one table
  1670.             local flattenedDefaultValues = {}
  1671.             local flattenedDefaultValuesTypes = {}
  1672.             for i, superAllDetails in ipairs(supersDetails) do
  1673.                 local superDetails = superAllDetails[mode]
  1674.                 -- go through ourself and super
  1675.                 for propertyName, defaultValue in pairs(superDetails.defaultValues) do
  1676.                     -- first add the override default values
  1677.                     if not flattenedDefaultValues[propertyName] then
  1678.                         flattenedDefaultValues[propertyName] = defaultValue
  1679.                     end
  1680.                 end
  1681.                 for propertyName, propertyDetails in pairs(superDetails.properties) do
  1682.                     -- then try to get the default from the property definitions
  1683.                     if not flattenedDefaultValues[propertyName] then
  1684.                         flattenedDefaultValues[propertyName] = propertyDetails.defaultValue
  1685.                     end
  1686.                     if not flattenedDefaultValuesTypes[propertyName] and propertyDetails.type then
  1687.                         flattenedDefaultValuesTypes[propertyName] = {propertyDetails.type, propertyDetails.allowsNil}
  1688.                     end
  1689.                 end
  1690.             end
  1691.             add("local %s={", defaultValuesID)
  1692.             for propertyName, defaultValue in pairs(flattenedDefaultValues) do
  1693.                 add("[%q]=loadstring(%q),", propertyName, "return " .. defaultValue)
  1694.             end
  1695.             add("}")
  1696.             local defaultValuesTypesString = "local %s={"
  1697.             for propertyName, typeDetails in pairs(flattenedDefaultValuesTypes) do
  1698.                 defaultValuesTypesString = defaultValuesTypesString .. string.format("[%q]={%s,%q},", propertyName, typeCheck(typeDetails[1], typeDetails[2]), typeDetails[1] .. (typeDetails[2] and " (allowsNil)" or ""))
  1699.             end
  1700.             add(defaultValuesTypesString .. "}", defaultValuesTypesID)
  1701.  
  1702.             -- add the instance variables
  1703.             local instanceVariablesID
  1704.             local instanceVariablesString = "local %s={"
  1705.             didAdd = false
  1706.             local addedInstanceVariables = {}
  1707.             for i, superDetails in ipairs(supersDetails) do
  1708.                 for variableName, content in pairs(superDetails[mode].instanceVariables) do
  1709.                     if not addedInstanceVariables[variableName] then
  1710.                         instanceVariablesString = instanceVariablesString .. string.format("[%q]=loadstring([[return %s]]),", variableName, escapeSquare(content))
  1711.                         didAdd = true
  1712.                         addedInstanceVariables[variableName] = true
  1713.                     end
  1714.                 end
  1715.             end
  1716.             if didAdd then
  1717.                 instanceVariablesID = nextID()
  1718.                 add(instanceVariablesString:sub(1,#instanceVariablesString - 1) .. "}", instanceVariablesID)
  1719.             end
  1720.  
  1721.             -- add the functions
  1722.             local functionIDs = {}
  1723.             local functionsTable = "local %s={"
  1724.             local didAdd = false
  1725.             local allFunctionNames = {} -- all of the function names defined by us and the supers
  1726.             for functionName, functionDetails in pairs(modeDetails.functions) do
  1727.                 -- first check that none of the parameters have same name as an instance variable (which would cause name clashes)
  1728.                 for i, parameterName in ipairs(functionDetails[6]) do
  1729.                     if addedInstanceVariables[parameterName] then
  1730.                         error("compiler error: parameter '" .. parameterName .. "' of " .. mode .. " function '" .. functionName .. "' in class '" .. className .. "' has the same name as an instance variable. Parameters cannot use the name of an instance variable.")
  1731.                     end
  1732.                 end
  1733.  
  1734.                 -- add the actual function code
  1735.                 local functionID = nextID()
  1736.                 local typeChecking = ""
  1737.                 for i, argumentDetails in ipairs(functionDetails[4]) do
  1738.                     if argumentDetails.defaultValue then
  1739.                         typeChecking = typeChecking .. string.format("if %s==nil then %s=%s end ", argumentDetails.name, argumentDetails.name, argumentDetails.defaultValue, argumentDetails.name, argumentDetails.defaultValue)
  1740.                     end
  1741.                     if not argumentDetails.isVarArg and argumentDetails.type then
  1742.                         typeChecking = typeChecking .. string.format("if %s(%s)then %s(self,%q,%q,%q,%s)end ", typeCheck(argumentDetails.type, argumentDetails.allowsNil), argumentDetails.name, functionErrorIDs.type.parameter, argumentDetails.name, functionName, argumentDetails.type .. (argumentDetails.allowsNil and " (allowsNil)" or ""), argumentDetails.name)
  1743.                     end
  1744.                 end
  1745.                 local functionBody = ""
  1746.                 local returnTypes = functionDetails[5]
  1747.                 for n, part in ipairs(functionDetails[2]) do
  1748.                     -- return vaule types
  1749.                     if type(part) == "string" then
  1750.                         functionBody = functionBody .. part
  1751.                     elseif type(part) == "table" then
  1752.                         -- these are return values
  1753.                         local cachedValues = {}
  1754.                         if returnTypes then
  1755.                             -- there are a set number of return types
  1756.                             local isVarArg = false
  1757.                             for i = 1, math.max(#part, #returnTypes) do
  1758.                                 local returnType = returnTypes[i]
  1759.                                 local value = part[i]
  1760.                                 if returnType and returnType.isVarArg then
  1761.                                     -- we don't care about vararg values' types
  1762.                                     break
  1763.                                 end
  1764.                                 if returnType and not value and not returnType.allowsNil then
  1765.                                     -- no value was given for this value, but there should have been one
  1766.                                     error("compiler error: invalid return value for function '" .. functionName .. "' #" .. i .. ", nothing was given but expected type '" .. returnType.type .. (returnType.allowsNil and " (allowsNil)" or "") .. "'")
  1767.                                 elseif returnType then
  1768.                                     -- this value is fine
  1769.                                     cachedValues[i] = true
  1770.                                     -- TODO: this is going to break with unpack..
  1771.                                     functionBody = functionBody .. string.format("local %s%d=%s if %s(%s%d)then %s(self,%q,%d,%s%d,%q)end ", idPrefix, i, value:gsub("^%s*", ""):gsub("%s*$", ""), typeCheck(returnTypes[i].type, returnTypes[i].allowsNil), idPrefix, i, functionErrorIDs.type.returned.func, functionName, i, idPrefix, i, returnType.type .. (returnType.allowsNil and " (allowsNil)" or ""))
  1772.                                 elseif not returnType and value then
  1773.                                     -- a value was given, but there should've been one
  1774.                                     error("compiler error: invalid return value for function '" .. functionName .. "' #" .. i .. ", '" .. value:gsub("^%s*", ""):gsub("%s*$", "") .. "' was given but expected nothing (i.e. too many return values)")
  1775.                                 end
  1776.                             end
  1777.                         end
  1778.                         functionBody = functionBody .. "return"
  1779.                         for i, value in ipairs(part) do
  1780.                             if cachedValues[i] then
  1781.                                 functionBody = functionBody .. (i == 1 and " " or "") .. idPrefix .. i .. (i ~= #part and "," or "")
  1782.                             else
  1783.                                 functionBody = functionBody .. value .. (i ~= #part and "," or "")
  1784.                             end
  1785.                         end
  1786.                     end
  1787.                 end
  1788.                 startLineNumbering(classDetails.fileEnvironment, functionDetails[3])
  1789.                 add("local function %s(self,super)return function%s%s%s end", functionID, functionDetails[1], typeChecking, functionBody)
  1790.                 stopLineNumbering()
  1791.                 functionIDs[functionName] = functionID
  1792.             end
  1793.             classDetails[mode].functionIDs = functionIDs
  1794.             for i, superDetails in ipairs(supersDetails) do
  1795.                 for functionName, id in pairs(superDetails[mode].functionIDs) do
  1796.                     allFunctionNames[functionName] = true
  1797.                 end
  1798.             end
  1799.             for functionName, _ in pairs(allFunctionNames) do
  1800.                 -- create the functions table (the list of the function and all its supers)
  1801.                 local functionsTableString = string.format("[%q]={", functionName)
  1802.                 local isConsecutive = true
  1803.                 for i, superDetails in ipairs(supersDetails) do
  1804.                     local superFunctionID = superDetails[mode].functionIDs[functionName]
  1805.                     if superFunctionID then
  1806.                         -- this super class has another version of the same function
  1807.                         if isConsecutive then
  1808.                             functionsTableString = functionsTableString .. string.format("%s,", superFunctionID)
  1809.                         else
  1810.                             functionsTableString = functionsTableString .. string.format("[%d]=%s,", i, superFunctionID)
  1811.                         end
  1812.                     else
  1813.                         isConsecutive = false
  1814.                     end
  1815.                 end
  1816.                 functionsTable = functionsTable .. functionsTableString .. "},"
  1817.                 didAdd = true
  1818.             end
  1819.             local functionsTableID
  1820.             if didAdd then
  1821.                 functionsTableID = nextID()
  1822.                 add(functionsTable:sub(1,#functionsTable - 1) .. "}", functionsTableID)
  1823.             end
  1824.  
  1825.             -- check that the functions align with the interfaces
  1826.             for i, interface in ipairs(interfaces) do
  1827.                 for functionName, interfaceFunctionDetails in pairs(interface[mode].functions) do
  1828.                     local functionDetails
  1829.                     for i, superDetails in ipairs(supersDetails) do
  1830.                         local superFunctionDetails = superDetails[mode].functions[functionName]
  1831.                         if superFunctionDetails then
  1832.                             functionDetails = superFunctionDetails
  1833.                             break
  1834.                         end
  1835.                     end
  1836.                     if not functionDetails then
  1837.                         error("compiler error: class '" .. className .. "' does not define " .. mode .. " function '" .. functionName .. "' required by interface '" .. interface.className .. "'")
  1838.                     else
  1839.                         for n, interfaceTypes in ipairs(interfaceFunctionDetails) do
  1840.                             local functionTypes = functionDetails[n + 3]
  1841.                             local name = n == 1 and "parameter" or "return value"
  1842.                             if #interfaceTypes ~= #functionTypes then
  1843.                                 error("compiler error: class '" .. className .. "' " .. mode .. " function '" .. functionName .. "' has a different number of " .. name .. "s (" .. #functionTypes .. ") than required by interface '" .. interface.className .. "' (" .. #interfaceTypes .. ").")
  1844.                             end
  1845.                             for j, interfaceType in ipairs(interfaceTypes) do
  1846.                                 local functionType = functionTypes[j]
  1847.                                 if interfaceType.allowsNil ~= functionType.allowsNil or interfaceType.type ~= functionType.type or interfaceType.isVarArg ~= functionType.isVarArg then
  1848.                                     error("compiler error: class '" .. className .. "' " .. mode .. " function '" .. functionName .. "' " .. name .. " #" .. j .. " is of a different type than the one required by interface '" .. interface.className .. "'.")
  1849.                                 end
  1850.                             end
  1851.                         end
  1852.                     end
  1853.                 end
  1854.             end
  1855.  
  1856.             -- add the properties
  1857.             -- start the index metatable
  1858.             local propertyMethodIDs = {}
  1859.             -- as with default values, we need to collate all the places where properties are defined and flatten it in to one table
  1860.             local flattenedProperties = {}
  1861.             local flattenedPropertyMethods = {}
  1862.             for i = #supersDetails, 1, -1 do
  1863.                 -- we need to go backwards because properties should only be defined ONCE (i.e. if it tries to override then error)
  1864.                 local superDetails = supersDetails[i][mode]
  1865.                 for propertyName, propertyDetails in pairs(superDetails.properties) do
  1866.                     -- first check that the property doesn't have same name as an instance variable (which would cause name clashes)
  1867.                     if addedInstanceVariables[propertyName] then
  1868.                         error("compiler error: " .. mode .. " property '" .. propertyName .. "' of class '" .. supersDetails[i].className .. "' has the same name as an instance variable. Properties cannot use the name of an instance variable.")
  1869.                     end
  1870.                     -- then try to get the default from the property definitions
  1871.                     if flattenedProperties[propertyName] then
  1872.                         error("compiler error: attempted to redeclare " .. mode .. " property '" .. propertyName .. " in " .. className .. " which was already defined by super class '" .. supersDetails[i].className .. "'. Subclasses are not allowed to change or redeclare properties defined by a super class.")
  1873.                     else
  1874.                         flattenedProperties[propertyName] = propertyDetails
  1875.                     end
  1876.                 end
  1877.  
  1878.                 -- flatten all the property methods
  1879.                 for methodName, methodDetails in pairs(superDetails.propertyMethods) do
  1880.                     for propertyName, content in pairs(methodDetails) do
  1881.                         if not flattenedPropertyMethods[propertyName] then
  1882.                             flattenedPropertyMethods[propertyName] = {}
  1883.                         end
  1884.                         if not flattenedPropertyMethods[propertyName][methodName] then
  1885.                             flattenedPropertyMethods[propertyName][methodName] = {[i] = content}
  1886.                         else
  1887.                             flattenedPropertyMethods[propertyName][methodName][i] = content
  1888.                         end
  1889.                     end
  1890.                 end
  1891.             end
  1892.  
  1893.             -- check that the properties align with the interfaces
  1894.             for i, interface in ipairs(interfaces) do
  1895.                 for propertyName, interfacePropertyDetails in pairs(interface[mode].properties) do
  1896.                     local compiledProperty = flattenedProperties[propertyName]
  1897.                     if not compiledProperty then
  1898.                         error("compiler error: class '" .. className .. "' does not define " .. mode .. " property '" .. propertyName .. "' required by interface '" .. interface.className .. "'")
  1899.                     else
  1900.                         for k, v in pairs(interfacePropertyDetails) do
  1901.                             if compiledProperty[k] ~= v then
  1902.                                 error("compiler error: class '" .. className .. "' " .. mode .. " property '" .. propertyName .. "' definition is different to the one required by interface '" .. interface.className .. "'. " .. k .. " was different.")
  1903.                             end
  1904.                         end
  1905.                     end
  1906.                 end
  1907.             end
  1908.  
  1909.             -- add all the property method function code
  1910.             local propertyMethodIDs = {}
  1911.             for methodName, methods in pairs(classDetails[mode].propertyMethods) do
  1912.                 for propertyName, methodDetails in pairs(methods) do
  1913.                     if not flattenedProperties[propertyName] then
  1914.                         error("compiler error: found property method '" .. methodName .. "' for undefined property '" .. propertyName .. "'!")
  1915.                     end
  1916.                     if not propertyMethodIDs[propertyName] then
  1917.                         propertyMethodIDs[propertyName] = {}
  1918.                     end
  1919.                     if methodName then
  1920.                         local methodID = nextID()
  1921.                         add("local function %s(self,super)return function%s end ", methodID, methodDetails[1])
  1922.                         propertyMethodIDs[propertyName][methodName] = methodID
  1923.                     end
  1924.                 end
  1925.             end
  1926.             classDetails[mode].propertyMethodIDs = propertyMethodIDs
  1927.  
  1928.             -- create the super tree for property methods
  1929.             local propertyMethodsTables = {get = "get={", didSet = "didSet={", willSet = "willSet={", set = "set={"}
  1930.             local didAddTable = {get = false, didSet = false, willSet = false, set = false}
  1931.             for propertyName, methods in pairs(flattenedPropertyMethods) do
  1932.                 propertyMethodsTableStrings = {get = string.format("[%q]={", propertyName), didSet = string.format("[%q]={", propertyName), willSet = string.format("[%q]={", propertyName), set = string.format("[%q]={", propertyName)}
  1933.                 for methodName, methodDetails in pairs(methods) do
  1934.                     local isConsecutive = true
  1935.                     for i, superDetails in ipairs(supersDetails) do
  1936.                         local superFunctionID = superDetails[mode].propertyMethodIDs[propertyName]
  1937.                         superFunctionID = superFunctionID and superFunctionID[methodName] -- only get the method if the super has any methods for that property name
  1938.                         if superFunctionID then
  1939.                             -- this super class has another version of the same function
  1940.                             if isConsecutive then
  1941.                                 propertyMethodsTableStrings[methodName] = propertyMethodsTableStrings[methodName] .. string.format("%s,", superFunctionID)
  1942.                             else
  1943.                                 propertyMethodsTableStrings[methodName] = propertyMethodsTableStrings[methodName] .. string.format("[%d]=%s,", i, superFunctionID)
  1944.                             end
  1945.                         else
  1946.                             isConsecutive = false
  1947.                         end
  1948.                     end
  1949.                     propertyMethodsTables[methodName] = propertyMethodsTables[methodName] .. propertyMethodsTableStrings[methodName]:sub(1,#propertyMethodsTableStrings[methodName] - 1) .. "},"
  1950.                     didAddTable[methodName] = true
  1951.                 end
  1952.             end
  1953.             local propertyMethodsTable = "local %s={"
  1954.             didAdd = false
  1955.             for methodName, didAddMethod in pairs(didAddTable) do
  1956.                 if didAddMethod then
  1957.                     didAdd = true
  1958.                     propertyMethodsTable = propertyMethodsTable .. propertyMethodsTables[methodName]:sub(1,#propertyMethodsTables[methodName] - 1) .. "},"
  1959.                 end
  1960.             end
  1961.             local propertyMethodsTableID
  1962.             if didAdd then
  1963.                 propertyMethodsTableID = nextID()
  1964.                 add(propertyMethodsTable:sub(1,#propertyMethodsTable - 1) .. "}", propertyMethodsTableID)
  1965.             end
  1966.  
  1967.             -- create the super tree tables for all the property methods
  1968.             local flattenedPropertyMethodIDs = {}
  1969.             local propertyMethodIndicies = {}
  1970.             local index = 1
  1971.             for propertyName, properties in pairs(flattenedPropertyMethods) do
  1972.                 flattenedPropertyMethodIDs[propertyName] = {}
  1973.                 propertyMethodIndicies[propertyName] = {}
  1974.                 for methodName, methods in pairs(properties) do
  1975.                     local lastI = 0
  1976.                     local didAddInner = false
  1977.                     for superI, methodDetails in pairs(methods) do
  1978.                         local superMethodID = supersDetails[superI][mode].propertyMethodIDs[propertyName]
  1979.                         if superMethodID then
  1980.                             superMethodID = superMethodID[methodName]
  1981.                         end
  1982.                         if superMethodID then
  1983.                             if not flattenedPropertyMethodIDs[propertyName][methodName] then
  1984.                                 flattenedPropertyMethodIDs[propertyName][methodName] = superMethodID
  1985.                                 propertyMethodIndicies[superMethodID] = index
  1986.                             end
  1987.                         end
  1988.                     end
  1989.                 end
  1990.             end
  1991.  
  1992.             -- create the instance __index & __newindex methods for accessing properties and functions
  1993.             local instanceIndexTableID
  1994.             local instanceIndexTable = "local %s=setmetatable({"
  1995.             local instanceNewindexTableID
  1996.             local instanceNewindexTable = "local %s=setmetatable({"
  1997.             local didAdd = false
  1998.             for propertyName, propertyDetails in pairs(flattenedProperties) do
  1999.                 local propertyMethods = flattenedPropertyMethods[propertyName]
  2000.                 -- add the index method
  2001.                 if propertyMethods and propertyMethods.get then
  2002.                     -- there is a custom index property method
  2003.                     instanceIndexTable = instanceIndexTable .. string.format("[%q]=function(_,k,v,g,l)", propertyName)
  2004.                     instanceIndexTable = instanceIndexTable .. "if l[k]then return v[k]else l[k]=true "
  2005.                     instanceIndexTable = instanceIndexTable .. "local v=g[k](_)l[k]=nil "
  2006.                     if propertyDetails.type ~= TYPE_ANY or not propertyDetails.allowsNil then
  2007.                         instanceIndexTable = instanceIndexTable .. string.format("if %s(v)then %s(_,k,%q,v)end ", typeCheck(propertyDetails.type, propertyDetails.allowsNil), functionErrorIDs.type.returned.get, propertyDetails.type .. (propertyDetails.allowsNil and " (allowsNil)" or ""))
  2008.                     end
  2009.                     instanceIndexTable = instanceIndexTable .. "return v end end,"
  2010.                 else
  2011.                     -- there aren't any methods, we just need to return the value
  2012.                     instanceIndexTable = instanceIndexTable .. string.format("[%q]=%s,", propertyName, functionBlankIndexID)
  2013.                 end
  2014.  
  2015.                 if propertyDetails.readOnly then
  2016.                     if propertyMethods and (propertyMethods.set or propertyMethods.didSet or propertyMethods.willSet) then
  2017.                         -- the property is readonly, yet there are setter property methods
  2018.                         error("compiler error: property '" .. propertyName .. "' of class '" .. className .. "' has willSet, set or didSet property methods, but the property is read only. These methods will never be called, remove them from your code.")
  2019.                     end
  2020.                     instanceNewindexTable = instanceNewindexTable .. string.format("[%q]=%s,", propertyName, functionErrorIDs.readOnly)
  2021.                 elseif propertyMethods and (propertyMethods.set or propertyMethods.didSet or propertyMethods.willSet) then
  2022.                     instanceNewindexTable = instanceNewindexTable .. string.format("[%q]=function(_,k,n,v,w,s,d,l)", propertyName)
  2023.                     if propertyDetails.type ~= TYPE_ANY or not propertyDetails.allowsNil then
  2024.                         instanceNewindexTable = instanceNewindexTable .. string.format("if %s(n)then %s(_,k,%q,n)end ", typeCheck(propertyDetails.type, propertyDetails.allowsNil), functionErrorIDs.type.property, propertyDetails.type .. (propertyDetails.allowsNil and " (allowsNil)" or ""))
  2025.                     end
  2026.                     instanceNewindexTable = instanceNewindexTable .. "if l[k]then v[k]=n else l[k]=true "
  2027.                     if propertyMethods.willSet then
  2028.                         instanceNewindexTable = instanceNewindexTable .. "w[k](_,n)"
  2029.                     end
  2030.                     if propertyMethods.set then
  2031.                         instanceNewindexTable = instanceNewindexTable .. "s[k](_,n)"
  2032.                     else
  2033.                         instanceNewindexTable = instanceNewindexTable .. "v[k]=n "
  2034.                     end
  2035.                     if propertyMethods.didSet then
  2036.                         instanceNewindexTable = instanceNewindexTable .. "d[k](_,n)"
  2037.                     end
  2038.                     instanceNewindexTable = instanceNewindexTable .. "l[k]=nil end end,"
  2039.                 else
  2040.                     -- there aren't any methods, we just need to set the value
  2041.                     instanceNewindexTable = instanceNewindexTable .. string.format("[%q]=%s,", propertyName, blankNewindexIDs(propertyDetails.type, propertyDetails.allowsNil))
  2042.                 end
  2043.                 didAdd = true
  2044.             end
  2045.             -- add the functions to the tables
  2046.             for functionName, _ in pairs(allFunctionNames) do
  2047.                 instanceNewindexTable = instanceNewindexTable .. string.format("[%q]=%s,", functionName, functionErrorIDs.metatable.func.newindex)
  2048.                 instanceIndexTable = instanceIndexTable .. string.format("[%q]=%s,", functionName, functionBlankIndexID)
  2049.             end
  2050.             if didAdd then
  2051.                 instanceIndexTableID = nextID()
  2052.                 instanceNewindexTableID = nextID()
  2053.                 add(instanceIndexTable:sub(1,#instanceIndexTable - 1) .. "},%s)", instanceIndexTableID, instanceMetatableMetatableID)
  2054.                 add(instanceNewindexTable:sub(1,#instanceNewindexTable - 1) .. "},%s)", instanceNewindexTableID, instanceMetatableMetatableID)
  2055.             else
  2056.                 instanceIndexTableID, instanceNewindexTableID = instanceMetatableMetatableID, instanceMetatableMetatableID
  2057.             end
  2058.  
  2059.             -- create the typeof tree and the list of supers
  2060.             local typeOfTreeString = "local %s={"
  2061.             local superNamesID
  2062.             local superNamesString = "local %s={"
  2063.             didAdd = false
  2064.             -- add the base classes & static instance
  2065.             for i, superDetails in ipairs(supersDetails) do
  2066.                 local superName = superDetails.className
  2067.                 typeOfTreeString = typeOfTreeString .. string.format("[%s]=1,", superName)
  2068.                 if mode == "static" then
  2069.                     -- add the static instances
  2070.                     typeOfTreeString = typeOfTreeString .. string.format("[%s]=1,", classStaticIDs[superName])
  2071.                 end
  2072.                 if i ~= 1 then
  2073.                     superNamesString = superNamesString .. string.format("%s,", classNamesIDs[superName])
  2074.                     didAdd = true
  2075.                 end
  2076.  
  2077.                 -- add the interfaces
  2078.                 for i, interfaceName in ipairs(superDetails.implements) do
  2079.                     typeOfTreeString = typeOfTreeString .. string.format("[%s]=1,", interfaceName)
  2080.                 end
  2081.             end
  2082.             local typeOfTreeID = nextID()
  2083.             add(typeOfTreeString:sub(1,#typeOfTreeString - 1) .. "}", typeOfTreeID)
  2084.             if didAdd then
  2085.                 superNamesID = nextID()
  2086.                 add(superNamesString:sub(1,#superNamesString - 1) .. "}", superNamesID)
  2087.             end
  2088.  
  2089.             -- add the class metatable
  2090.             if mode == "instance" then
  2091.                 metatableStrings[className] = {"setmetatable(%s,{__index=%s,__newindex=%s,__call=function(_,...)return %s({},%q,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,...)end,__tostring=function()return %s end})", className, valueTableId, functionErrorIDs.metatable.class.newindex, instanceCreateID, mode, instanceVariablesID, functionsTableID, defaultValuesID, defaultValuesTypesID, fileEnvironmentIDs[classDetails.fileEnvironment], classNamesIDs[className], instanceIndexTableID, instanceNewindexTableID, superNamesID, propertyMethodsTableID, typeOfTreeID, classTostringIDs[className]}
  2092.             elseif mode == "static" then
  2093.                 insert(staticInitialiseStrings, {"%s(%s,%q,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,...)",instanceCreateID, classStaticIDs[className], mode, instanceVariablesID, functionsTableID, defaultValuesID, defaultValuesTypesID, fileEnvironmentIDs[classDetails.fileEnvironment], classNamesIDs[className], instanceIndexTableID, instanceNewindexTableID, superNamesID, propertyMethodsTableID, typeOfTreeID})
  2094.             end
  2095.         end
  2096.     else
  2097.         add("setmetatable(%s,{__index=%s,__newindex=%s,__tostring=function()return %s end})", className, functionErrorIDs.metatable.interface.index, functionErrorIDs.metatable.interface.newindex, classTostringIDs[className])
  2098.     end
  2099. end
  2100.  
  2101. -- run the other code in the files inside their environments
  2102. for i, blocks in ipairs(fileEnvironments) do
  2103.     local id = fileEnvironmentIDs[i]
  2104.     for startLine, content in pairs(blocks) do
  2105.         if startLine ~= "fileName" then
  2106.             add("%s(%q,%d,setfenv(loadstring([[%s]]), %s))", functionErrorProxyID, blocks.fileName, startLine, escapeSquare(content), id)
  2107.         end
  2108.     end
  2109. end
  2110.  
  2111. -- load the enum values
  2112. for i, classDetails in pairs(classOrder) do
  2113.     if not classDetails.isInterface then
  2114.         local className = classDetails.className
  2115.         local enumIDs = classesEnumIDs[className]
  2116.         local enumTypeOfTableIDs = classesEnumTypeOfTableIDs[className]
  2117.         for enumName, content in pairs(classDetails.enums) do
  2118.             -- create enum's value table
  2119.             local enumTostringID = nextID()
  2120.             local enumID = enumIDs[enumName]
  2121.             add("%s=setfenv(loadstring([[return %s]]), %s)()", enumID, content.values, fileEnvironmentIDs[classDetails.fileEnvironment])
  2122.             add("local %s=\"enum '%s.%s': \" .. tostring(%s):sub(8)", enumTostringID, className, enumName, enumID)
  2123.             add("setmetatable(%s,{__index=%s,__newindex=%s,__tostring=function()return %s end})", enumID, functionErrorIDs.metatable.enum.index, functionErrorIDs.metatable.enum.newindex, enumTostringID)
  2124.  
  2125.             local typeOfTableID = enumTypeOfTableIDs[enumName]
  2126.             -- check the default values of the enum and create the type of table
  2127.             add("for k,v in pairs(%s)do if %s(v)then %s(%q,k,%q,v)end %s[v]=true end ", enumID, typeCheck(content.type, false), functionErrorIDs.type.enum, className .. "." .. enumName, content.type, typeOfTableID)
  2128.         end
  2129.         -- add the class' value table & metatable
  2130.         add(unpack(valueTableStrings[className]))
  2131.         add(unpack(metatableStrings[className]))
  2132.     end
  2133. end
  2134.  
  2135. -- initialise the statics
  2136. for i, initialiseString in ipairs(staticInitialiseStrings) do
  2137.     add(unpack(initialiseString))
  2138. end
  2139.  
  2140. -- add the error map
  2141. local lineNumberString = "local %s={"
  2142. didAdd = false
  2143. for lineNumber, errorPrefix in pairs(lineNumberMap) do
  2144.     lineNumberString = lineNumberString .. string.format("[%q]=%q,", lineNumber, errorPrefix)
  2145.     didAdd = true
  2146. end
  2147. if didAdd then
  2148.     local lineNumberID = nextID()
  2149.     add(lineNumberString:sub(1, #lineNumberString - 1) .. "}", lineNumberID)
  2150.     local filePathID = nextID()
  2151.     add("local _,__t=pcall(error,\"<@\",2)local %s=__t:match(\"^(.+):%%d+: <@\")", filePathID)
  2152.     add([[function catchErrors(f,...)local a = {...}local r = {pcall(function()return f(unpack(a))end)}if not r[1] then local e = r[2] local n,m=e:match("^"..%s .. ":(%%d+): (.+)$")if n then local p=%s[n]if p then error(p..m,0)end end error(e,0)else table.remove(r,1)return unpack(r)end end]], filePathID, lineNumberID)
  2153. end
  2154.  
  2155. print("Compiled in " .. os.clock() - compileStartTime .. "s!")
  2156.  
  2157. print("Saving...")
  2158. local h = io.open(outputPath, "w")
  2159. if h then
  2160.     h:write(file)
  2161.     h:close()
  2162. end
  2163. print("Saved. Compile complete.")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement