Skip to content

Instantly share code, notes, and snippets.

@RezzedUp
Last active June 25, 2021 00:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RezzedUp/e75847a72152871619eeec31f5700db2 to your computer and use it in GitHub Desktop.
Save RezzedUp/e75847a72152871619eeec31f5700db2 to your computer and use it in GitHub Desktop.
Skim Compiler build #177, bootstrapped of course. Requires Skript 2.2, SkQuery, and SkUtilities. Not recommended for use (yet)
# Skim-Compiler v0.0.1 compiled with Skim Compiler
#@: --> start 'plugins/Skript/projects/compiler/src/main.sk' @ 0
options:
SOURCE_DIRECTORY: plugins/Skript/projects
SCRIPTS_DIRECTORY: plugins/Skript/scripts
STORAGE: compile-data
#@: --> start 'plugins/Skript/projects/compiler/src/errors/Error.sk' @ 0
#
# ERRORS
#
# Append an error to a compile-data object.
# Returns the index of the error.
function appendCompilerError(data: text, error: text, file: text="", line: number=-1) :: text:
set {_size} to amount of {->%{_data}%::errors::*}
set {_index} to {_size} + 1
set {_index} to "%{_index}%"
set {->%{_data}%::errors::%{_index}%} to {_error}
if length of {_file} is greater than 0:
set {->%{_data}%::errors::%{_index}%::file} to {_file}
if {_line} is greater than 0:
set {->%{_data}%::errors::%{_index}%::line} to {_line}
return {_index}
function throwCompilerError(data: text, error: text, file: text="", line: number=-1) :: text:
set {_index} to appendCompilerError({_data}, {_error}, {_file}, {_line})
set {->%{_data}%::error-thrown} to {_index}
set {->%{_data}%::errors::%{_index}%::thrown} to true
return {_index}
sub "Skim:Compiler:print-errors":
set {_data} to parameter 1
set {_size} to amount of {->%{_data}%::errors::*}
{_size} is greater than 0
set {_to} to {->%{_data}%::sender}
if {->%{_data}%::error-thrown} is set:
set {_color} to "&c"
if {_size} is 1:
set {_amount} to "An error has occurred."
else:
set {_amount} to "Several errors have occurred."
else:
set {_color} to "&6"
if {_size} is 1:
set {_amount} to "A warning has occurred."
else:
set {_amount} to "Several warnings have occurred."
send "%{_color}%## --- [%script%.sk] --- %{_amount}%" to {_to}
loop {->%{_data}%::errors::*}:
set {_file} to {->%{_data}%::errors::%loop-index%::file}
set {_line} to {->%{_data}%::errors::%loop-index%::line}
set {_context} to ""
if {_file} is set:
set {_context} to "in %{_file}% "
if {_line} is set:
set {_context} to "%{_context}%@ line %{_line}%"
send "%{_color}%## &f%loop-value%" to {_to}
if length of {_context} is greater than 0:
send "%{_color}%## --> &7%{_context}%" to {_to}
delete {_file}
delete {_line}
send "%{_color}%## --- [end] ---" to {_to}
#
# Check if error is thrown
#
function canContinue(data: text) :: boolean:
if {->%{_data}%::error-thrown} is set:
return false
return true
function canContinueOtherwiseWarn(data: text) :: boolean:
set {_continue} to canContinue({_data})
if not {_continue}:
invoke "Skim:Compiler:print-errors" from {_data}
return {_continue}
#@: --> end 'plugins/Skript/projects/compiler/src/errors/Error.sk'
#@: --> resume 'plugins/Skript/projects/compiler/src/main.sk' @ 8
#@: --> start 'plugins/Skript/projects/compiler/src/project/ProjectConfig.sk' @ 0
function parseProjectConfig(data: text):
invoke "debug" from script and "Called parseProjectConfig(%{_data}%)"
set {_file} to {->%{_data}%::project-config}
if file {_file} doesn't exist:
throwCompilerError({_data}, "Missing project configuration file.")
stop
loop yaml nodes with keys "" from file {_file}:
set {_value} to yaml value "%loop-value%" from file {_file}
set {_value} to "%{_value}%"
if {_value} starts with "[":
if {_value} ends with "]":
invoke "debug" from script and "'%{_value}%' is a list"
set {_nodes::%loop-value%} to "list"
add yaml list "%loop-value%" from file {_file} to {_nodes::%loop-value%::values::*}
if {_nodes::%loop-value%} is not set:
if {_value} doesn't start with "MemorySection[":
set {_nodes::%loop-value%} to "value"
set {_nodes::%loop-value%::value} to {_value}
#! TODO: @debug section
loop {_nodes::*}:
set {_node} to loop-index
replace all "." in {_node} with "-"
if loop-value is "list":
invoke "debug" from script and "yaml node %loop-index% is a list: '%{_nodes::%loop-index%::values::*}%'"
set {->%{_data}%::%{_node}%} to true
set {->%{_data}%::%{_node}%::*} to {_nodes::%loop-index%::values::*}
else:
invoke "debug" from script and "yaml node %loop-index% is a value: '%{_nodes::%loop-index%::value}%'"
set {_value} to {_nodes::%loop-index%::value}
if {_value} is "true":
set {->%{_data}%::%{_node}%} to true
else if {_value} is "false":
set {->%{_data}%::%{_node}%} to false
else:
set {->%{_data}%::%{_node}%} to {_value}
#@: --> end 'plugins/Skript/projects/compiler/src/project/ProjectConfig.sk'
#@: --> resume 'plugins/Skript/projects/compiler/src/main.sk' @ 10
#@: --> start 'plugins/Skript/projects/compiler/src/Compiler.sk' @ 0
#@: --> start 'plugins/Skript/projects/compiler/src/Files.sk' @ 0
function getSourceFilePath(data: text, file: text) :: text:
set {_source} to {->%{_data}%::project-source}
set {_path} to "%{_source}%/%{_file}%"
if {_path} doesn't end with ".sk":
set {_path} to "%{_path}%.sk"
return {_path}
#@: --> end 'plugins/Skript/projects/compiler/src/Files.sk'
#@: --> resume 'plugins/Skript/projects/compiler/src/Compiler.sk' @ 1
#@: --> start 'plugins/Skript/projects/compiler/src/Preprocessor.sk' @ 0
function isAllowedByDebugState(data: text, file: text) :: boolean:
# Debugging is enabled, so allow all debugging.
if {->%{_data}%::debug} is true:
return true
# Debuggin isn't enabled and a debug section is open, so don't allow debugging.
if {->%{_data}%::files::%{_file}%::parser-options::debug} is set:
return false
# Allow everything else.
return true
#
# Parses the leading whitespace of a line.
# Submits results to: [data]->files->[file]->lines->[line]->whitespace
# Returns the remaining text.
#
function extractLeadingWhitespace(data: text, file: text, line: number, text: text) :: text:
set {_whitespace} to ""
if length of {_text} is 0:
set {_text} to ""
set {_chars::*} to {_text} split at ""
loop {_chars::*}:
if loop-value is " ":
set {_whitespace} to "%{_whitespace}%%loop-value%"
else:
set {->%{_data}%::files::%{_file}%::lines::%{_line}%::whitespace} to {_whitespace}
set {_whitespace-length} to length of {_whitespace}
set {_text-length} to length of {_text}
set {_return} to the last ({_text-length} - {_whitespace-length}) characters of {_text}
if {_return} isn't set:
set {_return} to ""
return {_return}
return {_text}
#
#
#
function parseCompilerDirective(data: text, file: text, line: number, text: text):
set {_submit} to "%{_data}%::files::%{_file}%"
set {_parts::*} to {_text} parsed as "@%string% <""?>%string%<""?>"
if amount of {_parts::*} is less than or equal to 0:
set {_parts::*} to {_text} parsed as "@%string%(\:| \-\-\>) %string%"
set {_directive} to {_parts::1}
set {_directive-text} to {_parts::2}
#
# @start "section"
# -- or --
# @begin "section"
#
# @end "section"
# -- or --
# @stop "section"
#
if {_directive} is "start" or "begin" or "end" or "stop":
if {_directive} is "start" or "begin":
set {->%{_submit}%::parser-options::%{_directive-text}%} to true
else:
delete {->%{_submit}%::parser-options::%{_directive-text}%}
stop
isAllowedByDebugState({_data}, {_file}) is true
#
# @debug: <an expression here>
# -- or --
# @debug --> <an expression here>
#
if {_directive} is "debug":
if {->%{_submit}%::parser-options::debug} isn't set:
set {_reset-parser-option} to true
set {->%{_submit}%::parser-options::debug} to true
if isAllowedByDebugState({_data}, {_file}):
set {->%{_submit}%::lines::%{_line}%::directive-submission} to {_directive-text}
if {_reset-parser-option} is set:
delete {->%{_submit}%::parser-options::debug}
#
# @include "local-project/path"
#
else if {_directive} is "include":
if length of {->%{_submit}%::lines::%{_line}%::whitespace} is greater than 0:
throwCompilerError({_data}, "Whitespace before '%{_directive}%' is invalid", {_file}, {_line})
stop
set {_include} to getSourceFilePath({_data}, {_directive-text})
# If it's already included, then there's no reason to include it again.
{->%{_data}%::includes::%{_include}%::is-included} is not set
# Checking for circular inclusions...
if {->%{_data}%::includes::%{_include}%} is set:
set {_within} to {->%{_data}%::includes::%{_include}%::within-file}
set {_at-line} to {->%{_data}%::includes::%{_include}%::at-line}
set {_warning} to "Invalid inclusion: '%{_text}%' because its inclusion from within '%{_within}%' at line %{_at-line}% is incomplete"
throwCompilerError({_data}, {_warning}, {_file}, {_line})
set {->%{_data}%::inclusion-error} to true
stop
set {->%{_data}%::includes::%{_include}%} to {_include}
set {->%{_data}%::includes::%{_include}%::within-file} to {_file}
set {->%{_data}%::includes::%{_include}%::at-line} to {_line}
invoke "Skim:Compiler:parse-file" from {_data} and {_include}
set {->%{_data}%::includes::%{_include}%::is-included} to true
if not canContinue({_data}):
{->%{_data}%::inclusion-error} is not set
throwCompilerError({_data}, "Invalid inclusion: '%{_text}%'", {_file}, {_line})
set {->%{_data}%::inclusion-error} to true
stop
else:
appendCompilerError({_data}, "Unknown directive: '%{_text}%'", {_file}, {_line})
#
#
#
function preprocessSkimLine(data: text, file: text, line: number, text: text) :: text:
if not canContinue({_data}):
return ""
#
# Parse a compiler directive.
#
if {_text} starts with "@":
parseCompilerDirective({_data}, {_file}, {_line}, {_text})
#
# Resets the {_text} variable if a directive submission exists.
# '@debug: send "done."' -> 'send "done."'
# Otherwise excludes the line.
#
if {->%{_data}%::files::%{_file}%::lines::%{_line}%::directive-submission} is set:
set {_text} to {->%{_data}%::files::%{_file}%::lines::%{_line}%::directive-submission}
else:
return "##@:exclude"
#
# Don't parse skim within a "raw" section.
#
if {->%{_data}%::files::%{_file}%::parser-options::raw} is set:
return {_text}
#
# Exclude all lines within a "debug" section if debugging is disabled.
#
if not isAllowedByDebugState({_data}, {_file}):
return "##@:exclude"
#
# Remove skim comments.
# '#@' -> '<exclude line>'
#
if {_text} starts with "##@":
return "##@:exclude"
#
# Replace empty function definitions.
# 'function thing():' -> 'function thing(~: boolean=true):'
#
if {_text} starts with "function ":
set {_function::*} to {_text} parsed as "function %string%\(\)%string%[<##?@.*>]"
amount of {_function::*} is more than or equal to 2
set {_function} to "function %{_function::1}%(~: boolean=true)"
set {_function-scope} to {_function::2}
if {_function-scope} starts with "::":
return "%{_function}% %{_function-scope}%"
else:
return "%{_function}%%{_function-scope}%"
#
# Replace empty returns with 'stop'
#
if {_text} is "return":
return "stop"
#
# Replace debug statements or remove them if debugging is disabled.
# 'debug "something"' -> 'invoke "debug" from script and "something"'
#
if {_text} starts with "debug ":
if {->%{_data}%::debug} is true:
set {_debug-text} to the last (length of {_text} - 6) characters of {_text}
return "invoke ""debug"" from script and %{_debug-text}%"
else:
return "##@:exclude"
return {_text}
#@: --> end 'plugins/Skript/projects/compiler/src/Preprocessor.sk'
#@: --> resume 'plugins/Skript/projects/compiler/src/Compiler.sk' @ 3
#
# COMPILATION
#
function parseSkimStatement(data: text, file: text, line: number, text: text, in-string: boolean=false, in-expression: boolean=false) :: text:
if not canContinue({_data}):
return ""
set {_active} to true
if {_in-string}:
if not {_in-expression}:
set {_active} to false
set {_characters::*} to {_text} split at ""
set {_index} to 0
set {_new} to ""
while {_index} is less than amount of {_characters::*}:
add 1 to {_index}
set {_char} to ""
set {_char} to {_characters::%{_index}%}
set {_next} to ""
set {_next-index} to {_index} + 1
set {_next} to {_characters::%{_next-index}%}
if {_char} is "[":
{_state::special} is not set
set {_char} to "{_"
else if {_char} is "]":
delete {_state::special}
set {_char} to "}"
else if {_char} is "$":
if {_next} is "[":
set {_state::special} to true
set {_char} to ""
set {_new} to "%{_new}%%{_char}%"
return {_new}
#
#
#
sub "Skim:Compiler:parse-line":
set {_data} to parameter 1
set {_file} to parameter 2
# TODO: complete
#
# Parses a line then submits to the output
#
function parseSkimLine(data: text, file: text, line: number) :: text:
set {_var} to "%{_data}%::files::%{_file}%::lines::%{_line}%"
set {_text} to extractLeadingWhitespace({_data}, {_file}, {_line}, {->%{_var}%})
set {_whitespace} to {->%{_var}%::whitespace}
set {_text} to preprocessSkimLine({_data}, {_file}, {_line}, {_text})
if not canContinue({_data}):
return ""
set {_text} to parseSkimStatement({_data}, {_file}, {_line}, {_text})
if {_text} starts with "##@:":
return {_text}
else:
return "%{_whitespace}%%{_text}%"
#
# Expected:
# Raw file contents at:
# [data]->files->[file]->lines::*
# Parsed output at:
# [data]->compiled-output->lines::*
#
sub "Skim:Compiler:parse-file":
set {_data} to parameter 1
set {_file} to parameter 2
if file {_file} doesn't exist:
throwCompilerError({_data}, "The file '%{_file}%' doesn't exist")
stop
add "##@: --> start '%{_file}%' @ 0" to {->%{_data}%::compiled-output::lines::*}
set {->%{_data}%::files::%{_file}%} to {_file}
set {->%{_data}%::files::%{_file}%::lines::*} to file contents of {_file}
add {_file} to {->%{_data}%::inclusion-stack::*}
set {_stack-index} to amount of {->%{_data}%::inclusion-stack::*}
loop {->%{_data}%::files::%{_file}%::lines::*}:
set {_line} to loop-index parsed as number
set {->%{_data}%::files::%{_file}%::current-line} to {_line}
set {_text} to ""
set {_text} to parseSkimLine({_data}, {_file}, {_line})
if not canContinue({_data}):
stop
{_text} is not "##@:exclude"
add {_text} to {->%{_data}%::compiled-output::lines::*}
add "##@: --> end '%{_file}%'" to {->%{_data}%::compiled-output::lines::*}
delete {->%{_data}%::inclusion-stack::%{_stack-index}%}
set {_stack-size} to amount of {->%{_data}%::inclusion-stack::*}
if {_stack-size} is greater than or equal to 1:
set {_resume} to {->%{_data}%::inclusion-stack::%{_stack-size}%}
set {_resume-line} to {->%{_data}%::files::%{_resume}%::current-line}
add "##@: --> resume '%{_resume}%' @ %{_resume-line}%" to {->%{_data}%::compiled-output::lines::*}
#
# COMPILE SCRIPTS
#
function compileProject(data: text):
set {_main-file} to {->%{_data}%::project-main}
set {_main} to getSourceFilePath({_data}, {_main-file})
if file {_main} doesn't exist:
throwCompilerError({_data}, "The main project file '%{_main}%' doesn't exist")
stop
set {->%{_data}%::includes::%{_main}%} to {_main}
set {->%{_data}%::includes::%{_main}%::within-file} to {_main}
set {->%{_data}%::includes::%{_main}%::at-line} to 0
set {_name} to {->%{_data}%::project-name}
set {_version} to {->%{_data}%::project-version}
add "## %{_name}% v%{_version}% compiled with Skim Compiler" to {->%{_data}%::compiled-output::lines::*}
invoke "Skim:Compiler:parse-file" from {_data} and {_main}
canContinue({_data}) is true
set {_output} to {->%{_data}%::output-directory}
if file {_output} isn't a directory:
create directory {_output}
invoke "debug" from script and "Created directory: %{_output}%"
set {_filename} to {->%{_data}%::output-filename}
set {_file} to "%{_output}%/%{_filename}%"
if file {_file} exists:
delete file {_file}
create file {_file}
invoke "debug" from script and "Created file: %{_file}%"
set file contents of {_file} to {->%{_data}%::compiled-output::lines::*}
invoke "Skim:Compiler:save-build" from {_data}
#
#
#
sub "Skim:Compiler:save-build":
set {_data} to parameter 1
{->%{_data}%::output-save-builds} is true
set {_output-dir} to {->%{_data}%::output-directory}
set {_build-dir} to "%{_output-dir}%/builds"
if file {_build-dir} isn't a directory:
create directory {_build-dir}
set {_latest-build-file} to "%{_build-dir}%/latest.build"
if file {_latest-build-file} doesn't exist:
create file {_latest-build-file}
set file contents of {_latest-build-file} to "0"
set {_latest-build-raw::*} to file contents of {_latest-build-file}
set {_latest-build-number} to "%{_latest-build-raw::1}%" parsed as number
set {_filename} to {->%{_data}%::output-filename}
replace ".sk" in {_filename} with ""
set {_saved} to false
while not {_saved}:
add 1 to {_latest-build-number}
set {_latest-build-filename} to "%{_filename}%-%{_latest-build-number}%.sk"
set {_latest-file} to "%{_build-dir}%/%{_latest-build-filename}%"
file {_latest-file} doesn't exist
set file contents of {_latest-build-file} to "%{_latest-build-number}%"
create file {_latest-file}
set file contents of {_latest-file} to {->%{_data}%::compiled-output::lines::*}
set {_saved} to true
#
#
#
function performCompilation(data: text):
set {_project-directory} to {->%{_data}%::project-directory}
if file "%{_project-directory}%" isn't a directory:
throwCompilerError({_data}, "The directory '%{_project-directory}%' does not exist")
else if {->%{_data}%::project-main} is not set:
throwCompilerError({_data}, "Missing 'main' entry in project.yml: cannot compile without main script", "project.yml")
canContinueOtherwiseWarn({_data}) is true
compileProject({_data})
canContinueOtherwiseWarn({_data}) is true
set {_name} to {->%{_data}%::project-name}
send "&aDone. &fCompiled %{_name}%" to {->%{_data}%::sender}
if {->%{_data}%::deploy-enabled}:
set {_deploy} to {->%{_data}%::deploy-directory}
set {_filename} to {->%{_data}%::output-filename}
set {_deploy-path} to "%{_deploy}%/%{_filename}%"
if file {_deploy-path} exists:
delete file {_deploy-path}
set {_output-directory} to {->%{_data}%::output-directory}
set {_output-file} to "%{_output-directory}%/%{_filename}%"
create file {_deploy-path}
set file contents of {_deploy-path} to {->%{_data}%::compiled-output::lines::*}
send "&bDeployed." to {->%{_data}%::sender}
execute console command "/sk reload %{_filename}%"
invoke "Skim:Compiler:print-errors" from {_data}
#@: --> end 'plugins/Skript/projects/compiler/src/Compiler.sk'
#@: --> resume 'plugins/Skript/projects/compiler/src/main.sk' @ 12
#@: --> start 'plugins/Skript/projects/compiler/src/CompileData.sk' @ 0
on script load:
delete {->{@STORAGE}::*}
on script unload:
delete {->{@STORAGE}::*}
every 10 minutes:
loop {->{@STORAGE}::*}:
if {->{@STORAGE}::%loop-value%::discard-data} is set:
delete {->{@STORAGE}::%loop-value%::*}
delete {->{@STORAGE}::%loop-value%}
#
# Compile Data Constructor
#
function newCompileData(name: text) :: text:
set {_data} to "{@STORAGE}::%{_name}%"
if {->%{_data}%} is set:
delete {->%{_data}%::*}
delete {->%{_data}%}
set {->%{_data}%} to {_name}
set {->%{_data}%::project-name} to {_name}
set {->%{_data}%::project-version} to "1.0.0"
set {_project-directory} to "{@SOURCE_DIRECTORY}/%{_name}%"
set {->%{_data}%::project-directory} to {_project-directory}
set {->%{_data}%::project-config} to "%{_project-directory}%/project.yml"
set {->%{_data}%::project-source} to "%{_project-directory}%/src"
set {->%{_data}%::output-directory} to "%{_project-directory}%/out"
set {->%{_data}%::output-filename} to "%{_name}%.sk"
set {->%{_data}%::scripts-directory} to "{@SCRIPTS_DIRECTORY}"
set {->%{_data}%::deploy-enabled} to true
set {->%{_data}%::deploy-directory} to "{@SCRIPTS_DIRECTORY}"
set {->%{_data}%::output-save-builds} to true
set {->%{_data}%::content} to "## Generated by compile.sk%nl%"
parseProjectConfig({_data})
return {_data}
#
# Discard data
#
function discardCompileData(data: text):
set {->%{_data}%::discard-data} to true
#@: --> end 'plugins/Skript/projects/compiler/src/CompileData.sk'
#@: --> resume 'plugins/Skript/projects/compiler/src/main.sk' @ 14
on script load:
if file "{@SOURCE_DIRECTORY}" isn't a directory:
create directory "{@SOURCE_DIRECTORY}"
command /compile <text> <boolean=false>:
aliases: /skript-compiler, /compiler, /skc, /skrc, /skriptc, /skriptcompiler
trigger:
set {_data} to newCompileData(arg-1)
set {->%{_data}%::sender} to sender
set {->%{_data}%::debug} to arg-2
performCompilation({_data})
command /diagnose <text> <integer>:
aliases: /find-line, /findline, /skim-diagnose, /skim-find-line, /skim-findline, /skimfindline
trigger:
set {_filename} to arg-1
set {_line} to arg-2
if {_line} is less than 1:
send "&c&oError:&f The line number must be greater than 0."
stop
if {_filename} doesn't end with ".sk":
set {_filename} to "%{_filename}%.sk"
set {_file} to "{@SCRIPTS_DIRECTORY}/%{_filename}%"
if file {_file} doesn't exist:
send "&c&oError:&f The file '%{_file}%' doesn't exist."
stop
set {_lines::*} to file contents of {_file}
set {_line-count} to amount of {_lines::*}
if {_line-count} is less than {_line}:
send "&c&oError:&f The file '%{_file}%' only has %{_line-count}% lines."
stop
set {_line-displacement} to 0
while {_line} is greater than 0:
set {_text} to ""
set {_text} to {_lines::%{_line}%}
delete {_meta::*}
if {_text} starts with "##@: --> ":
set {_meta::*} to {_text} parsed as "<##?>@: --\> %string% '%string%'"
set {_meta::*} to {_text} parsed as "<##?>@: --\> %string% '%string%' @ %integer%"
if {_meta::1} is "end":
set {_ignore::%{_meta::2}%} to {_meta::2}
else if {_meta::1} is "start" or "resume":
if {_meta::1} is "start":
if {_ignore::%{_meta::2}%} is set:
delete {_ignore::%{_meta::2}%}
amount of {_ignore::*} is less than or equal to 0:
set {_file-line} to {_line-displacement} + {_meta::3}
send "&3&oEstimated line location:&f Line %{_file-line}% in '%{_meta::2}%'"
stop
if amount of {_ignore::*} is less than or equal to 0:
add 1 to {_line-displacement}
set {_line} to {_line} - 1
send "&c&oError:&f Unable to locate the original location for line %arg-2% in file '%{_file}%' (was it compiled with skim?)"
#@: --> end 'plugins/Skript/projects/compiler/src/main.sk'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment