Compare commits

..

2 Commits

Author SHA1 Message Date
Jay a2fbd3032d Script Editor Bug fix 2020-04-26 19:22:43 +01:00
Jay 23c9c1cdb6 Script editor 2020-04-26 18:49:26 +01:00
3 changed files with 526 additions and 0 deletions

431
core/editor/lexer.lua Normal file
View File

@ -0,0 +1,431 @@
-- MIT License
--
-- Copyright (c) 2018 LoganDark
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
function lookupify(src, list)
list = list or {}
if type(src) == 'string' then
for i = 1, src:len() do
list[src:sub(i, i)] = true
end
elseif type(src) == 'table' then
for i = 1, #src do
list[src[i]] = true
end
end
return list
end
local base_ident = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_'
local base_digits = '0123456789'
local base_operators = '+-*/^%#'
local chars = {
whitespace = lookupify(' \n\t\r'),
validEscapes = lookupify('abfnrtv"\'\\'),
ident = lookupify(
base_ident .. base_digits,
{
start = lookupify(base_ident),
}
),
digits = lookupify(
base_digits,
{
hex = lookupify(base_digits .. 'abcdefABCDEF')
}
),
symbols = lookupify(
base_operators .. ',{}[]();.:', {
equality = lookupify('~=><'),
operators = lookupify(base_operators)
}
)
}
local keywords = {
structure = lookupify({
'and', 'break', 'do', 'else', 'elseif', 'end', 'for', 'function',
'goto', 'if', 'in', 'local', 'not', 'or', 'repeat', 'return', 'then',
'until', 'while'
}),
values = lookupify({
'true', 'false', 'nil'
})
}
return function(text)
local pos = 1
local start = 1
local buffer = {}
local lines = {}
local function look(delta)
delta = pos + (delta or 0)
return text:sub(delta, delta)
end
local function get()
pos = pos + 1
return look(-1)
end
local function getDataLevel()
local num = 0
while look(num) == '=' do
num = num + 1
end
if look(num) == '[' then
pos = pos + num + 1
return num
end
end
local function getCurrentTokenText()
return text:sub(start, pos - 1)
end
local currentLineLength = 0
local lineoffset = 0
local function pushToken(type, text)
text = text or getCurrentTokenText()
local tk = buffer[#buffer]
if not tk or tk.type ~= type then
tk = {
type = type,
data = text,
posFirst = start - lineoffset,
posLast = pos - 1 - lineoffset
}
if tk.data ~= '' then
buffer[#buffer + 1] = tk
end
else
tk.data = tk.data .. text
tk.posLast = tk.posLast + text:len()
end
currentLineLength = currentLineLength + text:len()
start = pos
return tk
end
local function newline()
lines[#lines + 1] = buffer
buffer = {}
get()
pushToken('newline')
buffer[1] = nil
lineoffset = lineoffset + currentLineLength
currentLineLength = 0
end
local function getData(level, type)
while true do
local char = get()
if char == '' then
return
elseif char == '\n' then
pos = pos - 1
pushToken(type)
newline()
elseif char == ']' then
local valid = true
for i = 1, level do
if look() == '=' then
pos = pos + 1
else
valid = false
break
end
end
if valid and look() == ']' then
pos = pos - level - 1
return
end
end
end
end
local function chompWhitespace()
while true do
local char = look()
if char == '\n' then
pushToken('whitespace')
newline()
elseif chars.whitespace[char] then
pos = pos + 1
else
break
end
end
pushToken('whitespace')
end
while true do
chompWhitespace()
local char = get()
if char == '' then
break
elseif char == '-' and look() == '-' then
pos = pos + 1
if look() == '[' then
pos = pos + 1
local level = getDataLevel()
if level then
getData(level, 'comment')
pos = pos + level + 2
pushToken('comment')
else
while true do
local char2 = get()
if char2 == '' or char2 == '\n' then
pos = pos - 1
pushToken('comment')
if char2 == '\n' then
newline()
end
break
end
end
end
else
while true do
local char2 = get()
if char2 == '' or char2 == '\n' then
pos = pos - 1
pushToken('comment')
if char2 == '\n' then
newline()
end
break
end
end
end
pushToken('comment')
elseif char == '\'' or char == '"' then
pushToken('string_start')
while true do
local char2 = get()
if char2 == '\\' then
pos = pos - 1
pushToken('string')
get()
local char3 = get()
if chars.digits[char3] then
for i = 1, 2 do
if chars.digits[look()] then
pos = pos + 1
end
end
elseif char3 == 'x' then
if chars.digits.hex[look()] and chars.digits.hex[look(1)] then
pos = pos + 2
else
pushToken('unidentified')
end
elseif char3 == '\n' then
pos = pos - 1
pushToken('escape')
newline()
elseif not chars.validEscapes[char3] then
pushToken('unidentified')
end
pushToken('escape')
elseif char2 == '\n' then
pos = pos - 1
pushToken('string')
newline()
break
elseif char2 == char or char2 == '' then
pos = pos - 1
pushToken('string')
get()
break
end
end
pushToken('string_end')
elseif chars.ident.start[char] then
while chars.ident[look()] do
pos = pos + 1
end
local word = getCurrentTokenText()
if keywords.structure[word] then
pushToken('keyword')
elseif keywords.values[word] then
pushToken('value')
else
pushToken('ident')
end
elseif chars.digits[char] or (char == '.' and chars.digits[look()]) then
if char == '0' and look() == 'x' then
pos = pos + 1
while chars.digits.hex[look()] do
pos = pos + 1
end
else
while chars.digits[look()] do
pos = pos + 1
end
if look() == '.' then
pos = pos + 1
while chars.digits[look()] do
pos = pos + 1
end
end
if look():lower() == 'e' then
pos = pos + 1
if look() == '-' then
pos = pos + 1
end
while chars.digits[look()] do
pos = pos + 1
end
end
end
pushToken('number')
elseif char == '[' then
local level = getDataLevel()
if level then
pushToken('string_start')
getData(level, 'string')
pushToken('string')
pos = pos + level + 2
pushToken('string_end')
else
pushToken('symbol')
end
elseif char == '.' then
if look() == '.' then
pos = pos + 1
if look() == '.' then
pos = pos + 1
end
end
if getCurrentTokenText():len() == 3 then
pushToken('vararg')
else
pushToken('symbol')
end
elseif char == ':' and look() == ':' then
get()
pushToken('label_start')
chompWhitespace()
if chars.ident.start[look()] then
get()
while chars.ident[look()] do
get()
end
pushToken('label')
chompWhitespace()
if look() == ':' and look(1) == ':' then
get()
get()
pushToken('label_end')
end
end
elseif chars.symbols.equality[char] then
if look() == '=' then
pos = pos + 1
end
pushToken('operator')
elseif chars.symbols[char] then
if chars.symbols.operators[char] then
pushToken('operator')
else
pushToken('symbol')
end
else
pushToken('unidentified')
end
end
lines[#lines + 1] = buffer
return lines
end

51
core/editor/main.lua Normal file
View File

@ -0,0 +1,51 @@
local lex = require("tevgit:core/editor/lexer.lua")
local theme = require("tevgit:core/editor/theme/default.lua")
local editor = teverse.construct("guiRichTextBox", {
parent = teverse.interface,
size = guiCoord(1, 0, 1, -100),
position = guiCoord(0, 0, 0, 50),
text = "local test = 10 + 10\n\n",
textWrap = true,
textFont = "tevurl:fonts/firaCodeRegular.otf",
textEditable = true
})
local function doLex()
editor:clearColours()
editor.backgroundColour = theme.background
editor.textColour = theme.foreground
local lines = lex(editor.text)
local index = 0
local lastColour = nil
for lineNumber, line in pairs(lines) do
local lineCount = 0
for _, token in pairs(line) do
local c = theme[token.type]
if c and lastColour ~= c then
editor:setColour(index + token.posFirst, c)
lastColour = c
end
lineCount = token.posLast
end
index = index + lineCount
end
end
-- Highlight any pre-entered text
doLex()
local lastStroke = nil
editor:on("keyUp", function()
if lastStroke then
return
end
-- Limit lexxer to once very 0.2 seconds (every keystroke is inefficient)
lastStroke = true
sleep(0.2)
doLex()
lastStroke = nil
end)

View File

@ -0,0 +1,44 @@
-- This file uses colours from https://ethanschoonover.com/solarized/
local colours = {
Base0 = colour.rgb(131, 148, 150),
Base1 = colour.rgb(147, 161, 161),
Base2 = colour.rgb(238, 232, 213),
Base3 = colour.rgb(253, 246, 227),
Base00 = colour.rgb(101, 123, 131),
Base01 = colour.rgb(88, 110, 117),
Base02 = colour.rgb(7, 54, 66),
Base03 = colour.rgb(0, 43, 54),
Yellow = colour.rgb(181, 137, 0),
Orange = colour.rgb(203, 75, 22),
Red = colour.rgb(220, 50, 47),
Magenta = colour.rgb(211, 54, 130),
Violet = colour.rgb(108, 113, 196),
Blue = colour.rgb(38, 139, 210),
Cyan = colour.rgb(42, 161, 152),
Green = colour.rgb(133, 153, 0),
}
return {
background = colours.Base03,
foreground = colours.Base0,
comment = colours.Base01,
string_start = colours.Cyan,
string_end = colours.Cyan,
string = colours.Cyan,
escape = colours.Blue,
keyword = colours.Green,
value = colours.Green,
ident = colours.Blue,
number = colours.Magenta,
symbol = colours.Base1,
vararg = colours.Red,
operator = colours.Green,
label_start = colours.Red,
label_end = colours.Red,
label = colours.Red,
unidentified = colours.Base01
}