I was looking for a lexer which was not part of a lua compiler. I was thinking of writing my own and started even doing so but thank god I found this. Now I just need to write a parser for it. But thank you!
This is very useful! This might seem like a stupid question, but why use _
before some properties of the nav
object (_RealIndex, _UserIndex and _ScanThread) but not in others (Source, TokenCache)?
Also in the Api & Usage section there’s a typo.
Fixed, thanks for catching that!
It’s general practice to use a _ prefix to denote “private” values. That is to say, when you use the nav object, you shouldn’t ever be directly interacting with nav._ScanThread
, because you risk breaking the nav if you alter it. Those values as used internally by the object itself, but aren’t recommended to be used by the higher level.
Source and TokenCache are “public” because I’ve found it’s useful to check what source is currently active and to directly index a token at a specific place (ex: checking the first token).
Hey guys! I’ve continued working on this!
I’ve refactored it (no more coroutines or return function list) and added contextual tokenizing (os.clock()
will highlight clock
as a builtin, and myThing.spawn()
will no longer highlight spawn
as the builtin function).
I’ve also released a highlighter module to go with this lexer! It uses RichText labels to highlight the code based on the tokens the lexer spits out.
Rather than continue to post into this obscure thread, I’ve opened a GitHub repository for it:
Lexer is breaking down code into different segments, often referred to as tokens according to token types (example keywords, operators, strings, brackets) and other criteria. This is used to parse, analyze, highlight and evaluate code syntax.
I like it. This lexer version is much more cleaner and understandable, I really didn’t like the coroutines that you were previously using. Hopefully with that change you will be able to add the newer and more complex syntax highlighting for methods, function names, properties and others that Luau has got in Studio. Good job!
Also, since it’s a GitHub repo, will other developers be able to contribute to this project as well?
Contribution is welcome! I will likely be adding more features as well. You can also open GitHub issues with suggestions/requests/problems!
Hey, I was wondering how you show this in a textbox so that the user can copy and paste it? Or is that not something this can be used for? If not then I’m still not sure how to even show it in a textlabel.
You can have a textbox, but the highlighted text has to be in a separate textlabel. Then just update the textlabel’s highlighting every time. the player inputs some text into the textbox. You could have the textlabel overlap the textbox or just put it next to it.
Ok, but I still have no clue how I use this, would you happen to know?
Do you want to know about the lexer or the highlighter?
The one that makes it looks like you’re looking at a script. Like changing print(“Hello World”) to what it would look like in an actual script.
This is the StarterGui Hierarchy i used
the module scripts are all located inside the github page that boatbomber has made.
Inside the Test local script i put this:
and this is what the GUI itself looks like:
I turned the property called “MultiLine” inside the TextBox on. (The textbox is the bottom gray line in the screen) Then when you click out of the textbox after typing something in, it will automatically put the highlighted text in the middle textlabel. Also, don’t mind the “print(‘Hello World’)” inside the text label, you don’t have to put that into the textlabel.
Also, the module script called “language” has a typo at line 26. There is a missing comma that you have to add, else it will fail to load.
I got an error saying Players.Wildcutepenguin.PlayerGui.BlocksGui.Highlighter:24: invalid argument #1 to 'gsub' (string expected, got Instance)
Edit: Sorry I just forgot to add something in the brackets, but now it’s giving me this instead.
Does your code look like this?
Highlighter
local Lexer = require(script.lexer)
local TokenColors = {}
local TokenFormats = {}
local ActiveLabels = {}
local LastText = {}
local Cleanups = {}
local function SanitizeRichText(s: string): string
return string.gsub(string.gsub(string.gsub(string.gsub(string.gsub(s,
"&", "&"),
"<", "<"),
">", ">"),
"\"", """),
"'", "'"
)
end
local function SanitizeTabs(s: string): string
return string.gsub(s, "\t", " ")
end
local function SanitizeControl(s: string): string
return string.gsub(s, "[\0\1\2\3\4\5\6\7\8\11\12\13\14\15\16\17\18\19\20\21\22\23\24\25\26\27\28\29\30\31]+", "")
end
local function highlight(textObject: Instance, src: string?, forceUpdate: boolean?)
src = SanitizeTabs(SanitizeControl(src or textObject.Text))
if forceUpdate ~= true and LastText[textObject] == src then
return
end
LastText[textObject] = src
textObject.RichText = false
textObject.Text = src
textObject.TextXAlignment = Enum.TextXAlignment.Left
textObject.TextYAlignment = Enum.TextYAlignment.Top
textObject.BackgroundColor3 = TokenColors.background
textObject.TextColor3 = TokenColors.iden
textObject.TextTransparency = 0.5
local lineFolder = textObject:FindFirstChild("SyntaxHighlights")
if not lineFolder then
lineFolder = Instance.new("Folder")
lineFolder.Name = "SyntaxHighlights"
lineFolder.Parent = textObject
end
local _, numLines = string.gsub(src, "\n", "")
numLines += 1
local textHeight = textObject.TextBounds.Y/numLines
local lineLabels = ActiveLabels[textObject]
if not lineLabels then
-- No existing lineLabels, create all new
lineLabels = table.create(numLines)
for i = 1, numLines do
local lineLabel = Instance.new("TextLabel")
lineLabel.Name = "Line_" .. i
lineLabel.RichText = true
lineLabel.BackgroundTransparency = 1
lineLabel.TextXAlignment = Enum.TextXAlignment.Left
lineLabel.TextYAlignment = Enum.TextYAlignment.Top
lineLabel.TextColor3 = TokenColors.iden
lineLabel.Font = textObject.Font
lineLabel.TextSize = textObject.TextSize
lineLabel.Size = UDim2.new(1, 0, 0, math.ceil(textHeight))
lineLabel.Position = UDim2.fromScale(0, textHeight * (i - 1) / textObject.AbsoluteSize.Y)
lineLabel.Text = ""
lineLabel.Parent = lineFolder
lineLabels[i] = lineLabel
end
else
for i=1, math.max(numLines, #lineLabels) do
local label = lineLabels[i]
if not label then
label = Instance.new("TextLabel")
label.Name = "Line_" .. i
label.RichText = true
label.BackgroundTransparency = 1
label.TextXAlignment = Enum.TextXAlignment.Left
label.TextYAlignment = Enum.TextYAlignment.Top
label.TextColor3 = TokenColors.iden
label.Font = textObject.Font
label.Parent = lineFolder
lineLabels[i] = label
end
label.Text = ""
label.TextSize = textObject.TextSize
label.Size = UDim2.new(1, 0, 0, math.ceil(textHeight))
label.Position = UDim2.fromScale(0, textHeight * (i - 1) / textObject.AbsoluteSize.Y)
end
end
local richText, index, lineNumber = {}, 0, 1
for token, content in Lexer.scan(src) do
local Color = TokenColors[token] or TokenColors.iden
local lines = string.split(SanitizeRichText(content), "\n")
for l, line in ipairs(lines) do
if l > 1 then
-- Set line
lineLabels[lineNumber].Text = table.concat(richText)
-- Move to next line
lineNumber += 1
index = 0
table.clear(richText)
end
index += 1
if Color ~= TokenColors.iden and string.find(line, "[%S%C]") then
richText[index] = string.format(TokenFormats[token], line)
else
richText[index] = line
end
end
end
-- Set final line
lineLabels[lineNumber].Text = table.concat(richText)
ActiveLabels[textObject] = lineLabels
local cleanup = Cleanups[textObject]
if not cleanup then
local connection
cleanup = function()
for _, label in ipairs(lineLabels) do
label:Destroy()
end
table.clear(lineLabels)
ActiveLabels[textObject] = nil
LastText[textObject] = nil
Cleanups[textObject] = nil
if connection then
connection:Disconnect()
end
end
Cleanups[textObject] = cleanup
connection = textObject.AncestryChanged:Connect(function()
if textObject.Parent then
return
end
cleanup()
end)
end
return cleanup
end
export type HighlighterColors = {
background: Color3?,
iden: Color3?,
keyword: Color3?,
builtin: Color3?,
string: Color3?,
number: Color3?,
comment: Color3?,
operator: Color3?
}
local function updateColors(colors: HighlighterColors?)
-- Setup color data
TokenColors.background = (colors and colors.background) or Color3.fromRGB(47, 47, 47)
TokenColors.iden = (colors and colors.iden) or Color3.fromRGB(234, 234, 234)
TokenColors.keyword = (colors and colors.keyword) or Color3.fromRGB(215, 174, 255)
TokenColors.builtin = (colors and colors.builtin) or Color3.fromRGB(131, 206, 255)
TokenColors.string = (colors and colors.string) or Color3.fromRGB(196, 255, 193)
TokenColors.number = (colors and colors.number) or Color3.fromRGB(255, 125, 125)
TokenColors.comment = (colors and colors.comment) or Color3.fromRGB(140, 140, 155)
TokenColors.operator = (colors and colors.operator) or Color3.fromRGB(255, 239, 148)
for key, color in pairs(TokenColors) do
TokenFormats[key] = '<font color="#'
.. string.format("%.2x%.2x%.2x", color.R * 255, color.G * 255, color.B * 255)
.. '">%s</font>'
end
-- Rehighlight existing labels using latest colors
for label, lineLabels in pairs(ActiveLabels) do
for _, lineLabel in ipairs(lineLabels) do
lineLabel.TextColor3 = TokenColors.iden
end
highlight(label, label.Text, true)
end
end
pcall(updateColors)
return setmetatable({
UpdateColors = updateColors,
Highlight = highlight
}, {
__call = function(_, textObject: Instance, src: string?)
return highlight(textObject, src)
end
})
lexer
--[=[
Lexical scanner for creating a sequence of tokens from Lua source code.
This is a heavily modified and Roblox-optimized version of
the original Penlight Lexer module:
https://github.com/stevedonovan/Penlight
Authors:
stevedonovan <https://github.com/stevedonovan> ----------- Original Penlight lexer author
ryanjmulder <https://github.com/ryanjmulder> ------------- Penlight lexer contributer
mpeterv <https://github.com/mpeterv> --------------------- Penlight lexer contributer
Tieske <https://github.com/Tieske> ----------------------- Penlight lexer contributer
boatbomber <https://github.com/boatbomber> --------------- Roblox port, added builtin token,
added patterns for incomplete syntax, bug fixes,
behavior changes, token optimization, thread optimization
Added lexer.navigator() for non-sequential reads
Sleitnick <https://github.com/Sleitnick> ----------------- Roblox optimizations
howmanysmall <https://github.com/howmanysmall> ----------- Lua + Roblox optimizations
List of possible tokens:
- iden
- keyword
- builtin
- string
- number
- comment
- operator
--]=]
local lexer = {}
local Prefix, Suffix, Cleaner = "^[%c%s]*", "[%c%s]*", "[%c%s]+"
local UNICODE = "[%z\x01-\x7F\xC2-\xF4][\x80-\xBF]+"
local NUMBER_A = "0x[%da-fA-F]+"
local NUMBER_B = "%d+%.?%d*[eE][%+%-]?%d+"
local NUMBER_C = "%d+[%._]?[%d_eE]*"
local OPERATORS = "[:;<>/~%*%(%)%-={},%.#%^%+%%]+"
local BRACKETS = "[%[%]]+" -- needs to be separate pattern from other operators or it'll mess up multiline strings
local IDEN = "[%a_][%w_]*"
local STRING_EMPTY = "(['\"])%1" --Empty String
local STRING_PLAIN = "(['\"])[^\n]-([^\\]%1)" --TODO: Handle escaping escapes
local STRING_INCOMP_A = "(['\"]).-\n" --Incompleted String with next line
local STRING_INCOMP_B = "(['\"])[^\n]*" --Incompleted String without next line
local STRING_MULTI = "%[(=*)%[.-%]%1%]" --Multiline-String
local STRING_MULTI_INCOMP = "%[=*%[.-.*" --Incompleted Multiline-String
local COMMENT_MULTI = "%-%-%[(=*)%[.-%]%1%]" --Completed Multiline-Comment
local COMMENT_MULTI_INCOMP = "%-%-%[=*%[.-.*" --Incompleted Multiline-Comment
local COMMENT_PLAIN = "%-%-.-\n" --Completed Singleline-Comment
local COMMENT_INCOMP = "%-%-.*" --Incompleted Singleline-Comment
-- local TYPED_VAR = ":%s*([%w%?%| \t]+%s*)" --Typed variable, parameter, function
local lang = require(script.language)
local lua_keyword = lang.keyword
local lua_builtin = lang.builtin
local lua_libraries = lang.libraries
local lua_matches = {
-- Indentifiers
{ Prefix .. IDEN .. Suffix, "var" },
-- Numbers
{ Prefix .. NUMBER_A .. Suffix, "number" },
{ Prefix .. NUMBER_B .. Suffix, "number" },
{ Prefix .. NUMBER_C .. Suffix, "number" },
-- Strings
{ Prefix .. STRING_EMPTY .. Suffix, "string" },
{ Prefix .. STRING_PLAIN .. Suffix, "string" },
{ Prefix .. STRING_INCOMP_A .. Suffix, "string" },
{ Prefix .. STRING_INCOMP_B .. Suffix, "string" },
{ Prefix .. STRING_MULTI .. Suffix, "string" },
{ Prefix .. STRING_MULTI_INCOMP .. Suffix, "string" },
-- Comments
{ Prefix .. COMMENT_MULTI .. Suffix, "comment" },
{ Prefix .. COMMENT_MULTI_INCOMP .. Suffix, "comment" },
{ Prefix .. COMMENT_PLAIN .. Suffix, "comment" },
{ Prefix .. COMMENT_INCOMP .. Suffix, "comment" },
-- Operators
{ Prefix .. OPERATORS .. Suffix, "operator" },
{ Prefix .. BRACKETS .. Suffix, "operator" },
-- Unicode
{ Prefix .. UNICODE .. Suffix, "iden" },
-- Unknown
{ "^.", "iden" },
}
--- Create a plain token iterator from a string.
-- @tparam string s a string.
function lexer.scan(s: string)
-- local startTime = os.clock()
lexer.finished = false
local index = 1
local sz = #s
local p1, p2, p3, pT = "", "", "", ""
return function()
if index <= sz then
for _, m in ipairs(lua_matches) do
local i1, i2 = string.find(s, m[1], index)
if i1 then
local tok = string.sub(s, i1, i2)
index = i2 + 1
lexer.finished = index > sz
--if lexer.finished then
-- print((os.clock()-startTime)*1000, "ms")
--end
local t = m[2]
local t2 = t
-- Process t into t2
if t == "var" then
-- Since we merge spaces into the tok, we need to remove them
-- in order to check the actual word it contains
local cleanTok = string.gsub(tok, Cleaner, "")
if lua_keyword[cleanTok] then
t2 = "keyword"
elseif lua_builtin[cleanTok] then
t2 = "builtin"
else
t2 = "iden"
end
if string.find(p1, "%.[%s%c]*$") and pT ~= "comment" then
-- The previous was a . so we need to special case indexing things
local parent = string.gsub(p2, Cleaner, "")
local lib = lua_libraries[parent]
if lib and lib[cleanTok] and not string.find(p3, "%.[%s%c]*$") then
-- Indexing a builtin lib with existing item, treat as a builtin
t2 = "builtin"
else
-- Indexing a non builtin, can't be treated as a keyword/builtin
t2 = "iden"
end
-- print("indexing",parent,"with",cleanTok,"as",t2)
end
end
-- Record last 3 tokens for the indexing context check
p3 = p2
p2 = p1
p1 = tok
pT = t2
return t2, tok
end
end
end
end
end
function lexer.navigator()
local nav = {
Source = "",
TokenCache = table.create(50),
_RealIndex = 0,
_UserIndex = 0,
_ScanThread = nil,
}
function nav:Destroy()
self.Source = nil
self._RealIndex = nil
self._UserIndex = nil
self.TokenCache = nil
self._ScanThread = nil
end
function nav:SetSource(SourceString)
self.Source = SourceString
self._RealIndex = 0
self._UserIndex = 0
table.clear(self.TokenCache)
self._ScanThread = coroutine.create(function()
for Token, Src in lexer.scan(self.Source) do
self._RealIndex += 1
self.TokenCache[self._RealIndex] = { Token, Src }
coroutine.yield(Token, Src)
end
end)
end
function nav.Next()
nav._UserIndex += 1
if nav._RealIndex >= nav._UserIndex then
-- Already scanned, return cached
return table.unpack(nav.TokenCache[nav._UserIndex])
else
if coroutine.status(nav._ScanThread) == "dead" then
-- Scan thread dead
return
else
local success, token, src = coroutine.resume(nav._ScanThread)
if success and token then
-- Scanned new data
return token, src
else
-- Lex completed
return
end
end
end
end
function nav.Peek(PeekAmount)
local GoalIndex = nav._UserIndex + PeekAmount
if nav._RealIndex >= GoalIndex then
-- Already scanned, return cached
if GoalIndex > 0 then
return table.unpack(nav.TokenCache[GoalIndex])
else
-- Invalid peek
return
end
else
if coroutine.status(nav._ScanThread) == "dead" then
-- Scan thread dead
return
else
local IterationsAway = GoalIndex - nav._RealIndex
local success, token, src = nil, nil, nil
for _ = 1, IterationsAway do
success, token, src = coroutine.resume(nav._ScanThread)
if not (success or token) then
-- Lex completed
break
end
end
return token, src
end
end
end
return nav
end
return lexer
language
local language = {
keyword = {
["and"] = true,
["break"] = true,
["continue"] = true,
["do"] = true,
["else"] = true,
["elseif"] = true,
["end"] = true,
["export"] = true,
["false"] = true,
["for"] = true,
["function"] = true,
["if"] = true,
["in"] = true,
["local"] = true,
["nil"] = true,
["not"] = true,
["or"] = true,
["repeat"] = true,
["return"] = true,
["self"] = true,
["then"] = true,
["true"] = true,
["type"] = true,
["typeof"] = true,
["until"] = true,
["while"] = true,
},
builtin = {
-- Luau Functions
["assert"] = true,
["error"] = true,
["getfenv"] = true,
["getmetatable"] = true,
["ipairs"] = true,
["loadstring"] = true,
["newproxy"] = true,
["next"] = true,
["pairs"] = true,
["pcall"] = true,
["print"] = true,
["rawequal"] = true,
["rawget"] = true,
["rawset"] = true,
["select"] = true,
["setfenv"] = true,
["setmetatable"] = true,
["tonumber"] = true,
["tostring"] = true,
["unpack"] = true,
["xpcall"] = true,
-- Luau Functions (Deprecated)
["collectgarbage"] = true,
-- Luau Variables
["_G"] = true,
["_VERSION"] = true,
-- Luau Tables
["bit32"] = true,
["coroutine"] = true,
["debug"] = true,
["math"] = true,
["os"] = true,
["string"] = true,
["table"] = true,
["utf8"] = true,
-- Roblox Functions
["DebuggerManager"] = true,
["delay"] = true,
["gcinfo"] = true,
["PluginManager"] = true,
["require"] = true,
["settings"] = true,
["spawn"] = true,
["tick"] = true,
["time"] = true,
["UserSettings"] = true,
["wait"] = true,
["warn"] = true,
-- Roblox Functions (Deprecated)
["Delay"] = true,
["ElapsedTime"] = true,
["elapsedTime"] = true,
["printidentity"] = true,
["Spawn"] = true,
["Stats"] = true,
["stats"] = true,
["Version"] = true,
["version"] = true,
["Wait"] = true,
["ypcall"] = true,
-- Roblox Variables
["File"] = true,
["game"] = true,
["plugin"] = true,
["script"] = true,
["shared"] = true,
["workspace"] = true,
-- Roblox Variables (Deprecated)
["Game"] = true,
["Workspace"] = true,
-- Roblox Tables
["Axes"] = true,
["BrickColor"] = true,
["CatalogSearchParams"] = true,
["CFrame"] = true,
["Color3"] = true,
["ColorSequence"] = true,
["ColorSequenceKeypoint"] = true,
["DateTime"] = true,
["DockWidgetPluginGuiInfo"] = true,
["Enum"] = true,
["Faces"] = true,
["FloatCurveKey"] = true,
["Font"] = true,
["Instance"] = true,
["NumberRange"] = true,
["NumberSequence"] = true,
["NumberSequenceKeypoint"] = true,
["OverlapParams"] = true,
["PathWaypoint"] = true,
["PhysicalProperties"] = true,
["Random"] = true,
["Ray"] = true,
["RaycastParams"] = true,
["Rect"] = true,
["Region3"] = true,
["Region3int16"] = true,
["RotationCurveKey"] = true,
["task"] = true,
["TweenInfo"] = true,
["UDim"] = true,
["UDim2"] = true,
["Vector2"] = true,
["Vector2int16"] = true,
["Vector3"] = true,
["Vector3int16"] = true,
},
libraries = {
-- Luau Libraries
bit32 = {
arshift = true,
band = true,
bnot = true,
bor = true,
btest = true,
bxor = true,
countlz = true,
countrz = true,
extract = true,
lrotate = true,
lshift = true,
replace = true,
rrotate = true,
rshift = true,
},
coroutine = {
close = true,
create = true,
isyieldable = true,
resume = true,
running = true,
status = true,
wrap = true,
yield = true,
},
debug = {
dumpheap = true,
info = true,
loadmodule = true,
profilebegin = true,
profileend = true,
resetmemorycategory = true,
setmemorycategory = true,
traceback = true,
},
math = {
abs = true,
acos = true,
asin = true,
atan2 = true,
atan = true,
ceil = true,
clamp = true,
cos = true,
cosh = true,
deg = true,
exp = true,
floor = true,
fmod = true,
frexp = true,
ldexp = true,
log10 = true,
log = true,
max = true,
min = true,
modf = true,
noise = true,
pow = true,
rad = true,
random = true,
randomseed = true,
round = true,
sign = true,
sin = true,
sinh = true,
sqrt = true,
tan = true,
tanh = true,
huge = true,
pi = true,
},
os = {
clock = true,
date = true,
difftime = true,
time = true,
},
string = {
byte = true,
char = true,
find = true,
format = true,
gmatch = true,
gsub = true,
len = true,
lower = true,
match = true,
pack = true,
packsize = true,
rep = true,
reverse = true,
split = true,
sub = true,
unpack = true,
upper = true,
},
table = {
clear = true,
clone = true,
concat = true,
create = true,
find = true,
foreach = true,
foreachi = true,
freeze = true,
getn = true,
insert = true,
isfrozen = true,
maxn = true,
move = true,
pack = true,
remove = true,
sort = true,
unpack = true,
},
utf8 = {
char = true,
codepoint = true,
codes = true,
graphemes = true,
len = true,
nfcnormalize = true,
nfdnormalize = true,
offset = true,
charpattern = true,
},
-- Roblox Libraries
Axes = {
new = true,
},
BrickColor = {
Black = true,
Blue = true,
DarkGray = true,
Gray = true,
Green = true,
new = true,
New = true,
palette = true,
Random = true,
random = true,
Red = true,
White = true,
Yellow = true,
},
CatalogSearchParams = {
new = true,
},
CFrame = {
Angles = true,
fromAxisAngle = true,
fromEulerAngles = true,
fromEulerAnglesXYZ = true,
fromEulerAnglesYXZ = true,
fromMatrix = true,
fromOrientation = true,
lookAt = true,
new = true,
identity = true,
},
Color3 = {
fromHex = true,
fromHSV = true,
fromRGB = true,
new = true,
toHSV = true,
},
ColorSequence = {
new = true,
},
ColorSequenceKeypoint = {
new = true,
},
DateTime = {
fromIsoDate = true,
fromLocalTime = true,
fromUniversalTime = true,
fromUnixTimestamp = true,
fromUnixTimestampMillis = true,
now = true,
},
DockWidgetPluginGuiInfo = {
new = true,
},
Enum = {},
Faces = {
new = true,
},
FloatCurveKey = {
new = true,
},
Font = {
fromEnum = true,
new = true,
},
Instance = {
new = true,
},
NumberRange = {
new = true,
},
NumberSequence = {
new = true,
},
NumberSequenceKeypoint = {
new = true,
},
OverlapParams = {
new = true,
},
PathWaypoint = {
new = true,
},
PhysicalProperties = {
new = true,
},
Random = {
new = true,
},
Ray = {
new = true,
},
RaycastParams = {
new = true,
},
Rect = {
new = true,
},
Region3 = {
new = true,
},
Region3int16 = {
new = true,
},
RotationCurveKey = {
new = true,
},
task = {
cancel = true,
defer = true,
delay = true,
desynchronize = true,
spawn = true,
synchronize = true,
wait = true,
},
TweenInfo = {
new = true,
},
UDim = {
new = true,
},
UDim2 = {
fromOffset = true,
fromScale = true,
new = true,
},
Vector2 = {
new = true,
one = true,
xAxis = true,
yAxis = true,
zero = true,
},
Vector2int16 = {
new = true,
},
Vector3 = {
fromAxis = true,
FromAxis = true,
fromNormalId = true,
FromNormalId = true,
new = true,
one = true,
xAxis = true,
yAxis = true,
zAxis = true,
zero = true,
},
Vector3int16 = {
new = true,
},
},
}
-- Filling up language.libraries.Enum table
local enumLibraryTable = language.libraries.Enum
for _, enum in ipairs(Enum:GetEnums()) do
enumLibraryTable[tostring(enum)] = true --TODO: Remove tostring from here once there is a better way to get the name of an Enum
end
return language
Yeah, so it must be when I’m passing something.
For testing purposes I just did this:
local Highighter = require(script.Parent.Highlighter)
wait(5)
Highighter:Highlight(script.Parent.TextLabel)
When doing Highlighter:Highlight, the first parameter is a string and not an object. Just do Highlighter(TextLabel)
Woah it actually worked, is there a light mode for this?
My bad, when you do a colon the first parameter is Highlighter, if you do Highlighter.Highlight(TextLabel, TEXT_HERE) it should work too