Flipeador wrote:No estoy en casa en este momento, no puedo comprobar nada. Aún así, ¿Cual es la ventaja de esto?, ¿No crees que utilizar Include de forma explicita reduce confusiones?.
Usar include explicito a veces reduce confusion y a veces no, a veces ahorra tiempo y a veces no. Usar include explicito te obliga a incluir todo lo demas que haya en un library file, si necesitas una funcion nada mas, tienes que traer todo. Asi las funciones nunca se ejecuten, el interpreter de autohotkey les hace parsing con syntax checking y todo.
Si tienes una libreria bien modular donde bastantes de las funciones son snippets, o wrappers en sus files individuales, la inclusion manual es tediosa. Hay funciones que de por si merecen ser autoincluidas cuando son bien streamlined y self-explanatory, o cuando quieres proveer una funcionalidad default poniendolas en el standard library (lib directory in autohotkey.exe's directory), en especial WinAPI wrappers around DllCalls.
Classes, funciones co-dependientes o bien complejas, con toda la razon deberian ser incluidas explicitamente. De resto, tener que hacer muchos includes, o tener que embolsar funciones en un solo file, que trae los problemas relaciones de falta de modularidad, pierde la comodidad que esto ofrece.
Esto esta incluido en Autohotkey como una funcionalidad de comodidad (quality of life) y ya tiene tiempo incluido. A la hora de compilar no hay confusion ya que normalmente el compiler incluye todo. En dado caso no seria cuestion de "ventaja de esto" es cuestion de que si alguien desea usar tu compiler, y esta contando con la autoinclusion de funciones simples que esta documentado en el manual de autohotkey, no van a funcionar. Menciono esto ya que lo tienes publicado en tu signature i.e advertisement, asi que me imagino que quisieras que otras personas tambien lo usen. Al menos de Lexikos te haya dicho que va a quitar los autoincludes y iLib, estarias yendo contra funcionalidad documentada.
Flipeador wrote:Lo de comprobar sintaxis utilizando la versión actual de AHK instalada no lo quise implementar en un principio porque lo veía innecesario
No es la version instalada, es la version que este en el directorio arriba de ahk2exe o la que este corriendo ahk2exe (o incluyendo una nueva, la que este en el mismo directory de ahk2exe). No es la que sale en el registro.
El syntax checking es byproduct de usar /iLib para incluir autoincludes, es un bono. La syntax deberia (o tiene) que hacer matching con el interpreter de autohotkey. Autohotkey.exe hace su propio syntax checking during runtime, lo cual garantiza 100% compatibilidad de lo que escribiste, y no tienes forma de deshabilitarlo. Igual va a ir linea por linea revisando todo, asi no haya comentarios o blank lines o todo este incluido. Por lo cual en dado caso es otro "quality of life" a la hora de compilar.
Flipeador wrote:complicaba el código fuente, el cual trato de mantener lo mas claro y limpio posible, además, para llevar a cabo eso, debo utilizar archivos temporales, que no me termina de convencer. Aún así, lo voy a estar revisado, aún queda MUCHO por mejorar el compilador.
Lo de los archivos temporales lo entiendo. Hace unas horas le hice a Lexikos un commit para que permita que los scripts arrancados por StdIn, puedan usar el local library asumido en el original working directory. El behavior actual es que el a_scriptdir se pone en blanco en runtime y no se pueden cargas los libs locales usando StdIn.
Por ejemplo /iLib * te deja leer el output en StdOut, y /ErrorStdOut, te deja usar leer errores por StdErr y usando * como script path y writing a StdIn el script (NewCode variable) te deja correrla sin tener que usar para nada temporary files.
Este es el example snippet de la seccion abajo "; Terminar y devolver el código procesado" en el condicional de if (Tree == ""), que me esta funcionando a mi, sin usar ningun temp file, para que mas o menos tengas una idea. Es largo por los comentarios detallando todo, de por si no ocupa mucho espacio. Cuando tengas algun tiempo lo revisas a ver.
Usando la clase "Subprocess" de cocobelgica que estaria en su propio file en Lib e incluida al final de scriptparser.
Esta usando el pull request
https://github.com/Lexikos/AutoHotkey_L/pull/102
Code: Select all
If (Tree == "") ; ¿estamos procesando el script principal que se va a compilar?
{
;// This entire behavior could be overriden by a command line switch, something like /nocheck,
;// or a checkbox in the GUI, and it would never take place if wrapped inside an if.
local ahkexe:="" ;// Will contain Autohotkey.exe after lookup (this may not be necessary, since we may already have the autohotkey.exe in a variable,
;// but I don't know which one could be similar to the checks below for the path.).
;// If this is done already somewhere else we can simply use that variable.
local script_streams := "" ;// Will contains subprocess object
local script_stderr := "" ;// Will contain the readout of stderr from subprocess object
local script_stdout := "" ;// Will contain the readout of stdout from subprocess object
if A_IsCompiled ;// Ahk2Exe is compiled
{
;// As mentioned above, we may already have the executable stored somewhere, or maybe not, so as an example, get one.
if !FileExist(ahkexe := A_ScriptDir "\..\AutoHotkey.exe") ;// In parent dir
&& !FileExist(ahkexe := A_ScriptDir "\AutoHotkey.exe") ;// In same dir
ahkexe := ""
}
else ;// Ahk2exe is not compiled
ahkexe := A_AhkPath ;// Use same exe that's running Ahk2Exe.ahk
if (ahkexe)
{
Loop 2
{
;// This is done inside a loop. First time will run NewCode, get StdOut and include any missing autoincludes,
;// but we don't grab StdErr (since missing functions are considered errors). Should be done no more than two times.
;// Second time will rerun NewCode again after autoincludes are included, will clear out script_stdout and break out of the loop.
;// This is done to have an up-to-date Stderr below, that will check if there are any true syntax errors.
;// (Functions that do not exist anywhere will error for example).
;// Script streams, object from SubProcess by Cocobelgica.
;// A modified version of Lexikos' ExecScript() can be used for smaller code, but this guarantees
;// that no CMD window shows.
;// Using a_space in string below to denote the separation of each element and the use of internal double quotes.
;// /iLib * is StdOut, last * is script StdIn.
script_streams := new SubProcess('"' ahkexe '"' . a_space . '/iLib * /ErrorStdOut *')
script_streams.StdIn.Write(NewCode) ;// Write current, newcode i.e the new "cleaned and fixed" script
script_streams.StdIn.Close() ;// Close StdIn so it can run.
;// StdOut will contain the output of the iLib command. Essentially the pairs of #Include and #IncludeAgain.
;// Do this first before checking the syntax again. Since missing function definitions (not automatically included,
;// are considered errors and reported in stderr.
;// We want to include all missing functions.
if ( script_stdout := script_streams.StdOut.ReadAll() ) ;// Grab StdOut (coming from iLib)
{
Loop parse, script_stdout, "`r`n"
{
loopField:= a_loopField ;// Quality of life
if !loopField
{
;// Just decrease a_index otherwise it gets all borked due to skipping new lines,
;// where a_loopfield becomes empty, but its still counted.
;// This way modulo function below works properly.
--a_index
continue
}
;// The iLib file is fixed syntax. There's no ambiguity here. It will always have the same layout.
if (Mod(a_index, 2) != 0) ;// Number is odd (#include dir line)
{
A_Workingdir := RTrim(SubStr(loopField, 10), "\") ;// Right side of #Include
;// It's a path to a directory, use it to set A_WorkingDir as is done in previous #include checks.
}
else if (Mod(a_index, 2) = 0) ;// Number is even (#includeagain file line)
{
NewCode .= PreprocessScript(SubStr(loopField, 15), Tree . "`n" . Script, FileList, Directives) . "`n"
;// SubStr(loopField, 15) is right side of #IncludeAgain excluding space.
;// Since the iLib file is #IncludeAgain, above PreProcessScript call doesn't check IsAlreadyIncluded()
;// and is done unconditionally.
}
}
script_stdout:="" ;// Blank it just in case. May not be necessary.
}
else
break
}
;// StdErr will contain any true syntax errors, i.e the output of ErrorStdOut which is the exception autohotkey would have thrown,
;// as checked by Autohotkey.exe. Will work across versions.
;// If there are function calls that are not manually included and do not exist in any library, they will show here.
;// This would be after the compiler does it's own thing (like change non-autohotkey compatible comments).
if ( script_stderr := script_streams.StdErr.ReadAll() ) ;// Grab StdErr (coming from ErrorStdOut)
{
Util_Error("Script contains syntax errors with respect to the used AutoHotkey binary.", script_stderr, true)
;// Syntax errors could be displayed as a GUI or in msgbox (as done currently, but may be be too big for msgbox if there are too many),
;// to provide more data to the user. Or the user can simply run the ahk file, which should have been done for testing.
;// Or perhaps the user is using the wrong binary for the syntax he's using (different minor version of autohotkey).
;// Either way, this will check that the script has proper syntax for the chosen binary file,
;// as error precheck, so the user doesn't have to run the compiled exe to see if its valid.
}
}
Return { Code: "; <COMPILER: v" . A_AhkVersion . ">`n" . Trim(NewCode, "`t`s`r`n")
, Directives: Directives
, Script: Script }
} ;// Ending of (Tree == "")
Este es subprocess de cocobelgica, de alternativa puede ser una custom version de ExeScript() de Lexikos (en la documentacion de autohotkey)
Code: Select all
class Subprocess
{
__New(cmd, cwd:="")
{
DllCall("CreatePipe", "Ptr*", stdin_read, "Ptr*", stdin_write, "Ptr", 0, "UInt", 0)
DllCall("SetHandleInformation", "Ptr", stdin_read, "UInt", 1, "UInt", 1)
DllCall("CreatePipe", "Ptr*", stdout_read, "Ptr*", stdout_write, "Ptr", 0, "UInt", 0)
DllCall("SetHandleInformation", "Ptr", stdout_write, "UInt", 1, "UInt", 1)
DllCall("CreatePipe", "Ptr*", stderr_read, "Ptr*", stderr_write, "Ptr", 0, "UInt", 0)
DllCall("SetHandleInformation", "Ptr", stderr_write, "UInt", 1, "UInt", 1)
static _STARTUPINFO
if !VarSetCapacity(_STARTUPINFO)
{
sizeof_si := A_PtrSize==8 ? 104 : 68 ; 40 + 7*A_PtrSize + 2*(pad := A_PtrSize==8 ? 4 : 0)
VarSetCapacity(_STARTUPINFO, sizeof_si, 0)
NumPut(sizeof_si, _STARTUPINFO, "UInt")
NumPut(0x100, _STARTUPINFO, A_PtrSize==8 ? 60 : 44, "UInt") ; dwFlags=STARTF_USESTDHANDLES)
}
NumPut(stderr_write, NumPut(stdout_write, NumPut(stdin_read, _STARTUPINFO, A_PtrSize==8 ? 80 : 56)))
static sizeof_pi := 8 + 2*A_PtrSize
this.SetCapacity("PROCESS_INFORMATION", sizeof_pi)
proc_info := this.GetAddress("PROCESS_INFORMATION")
if IsObject(cmd)
{
static quot := Func("Format").Bind("{1}{2}{1}", Chr(34))
length := cmd.Length(), args := cmd, cmd := ""
for i, arg in args
cmd .= (InStr(arg, " ") ? quot.Call(arg) : arg) . (i<length ? " " : "")
}
success := DllCall("CreateProcess", "Ptr", 0, "Str", cmd, "Ptr", 0
, "Ptr", 0, "Int", 1, "UInt", 0x8000000, "Ptr", 0, "Str", cwd=="" ? A_WorkingDir : cwd
, "Ptr", &_STARTUPINFO, "Ptr", proc_info)
if (!success)
throw Exception("Failed to create process.", -1, cmd)
this.StdIn := new Subprocess.StreamWriter(stdin_write)
this.StdOut := new Subprocess.StreamReader(stdout_read)
this.StdErr := new Subprocess.StreamReader(stderr_read)
DllCall("CloseHandle", "Ptr", stdin_read)
DllCall("CloseHandle", "Ptr", stdout_write)
DllCall("CloseHandle", "Ptr", stderr_write)
}
__Delete()
{
proc_info := this.GetAddress("PROCESS_INFORMATION")
DllCall("CloseHandle", "Ptr", NumGet(proc_info + 0)) ; hProcess
DllCall("CloseHandle", "Ptr", NumGet(proc_info + A_PtrSize)) ; hThread
}
__Handle[] ; hProcess
{
get {
return NumGet(this.GetAddress("PROCESS_INFORMATION"))
}
}
ProcessID[]
{
get {
return NumGet(this.GetAddress("PROCESS_INFORMATION") + 2*A_PtrSize, "UInt") ; dwProcessId
}
}
Status[] ; Running=0 , Done=1
{
get {
return !(this.ExitCode == 259) ; STILL_ACTIVE=259
}
}
ExitCode[] ; STILL_ACTIVE=259
{
get {
if DllCall("GetExitCodeProcess", "Ptr", this.__Handle, "UInt*", exit_code)
return exit_code
}
}
Terminate(exit_code:=0)
{
if (exit_code == 259) ; STILL_ACTIVE
throw Exception("Exit code 'STILL_ACTIVE' is reserved", -1, exit_code)
; use gentler method - attempt to close window(s) first
prev_dhw := A_DetectHiddenWindows
DetectHiddenWindows On
hwnd_lfw := WinExist() ; store current Last Found window
wintitle := "ahk_pid " . this.ProcessID
while (hwnd := WinExist(wintitle)) {
WinClose
if WinExist("ahk_id " . hwnd)
WinKill
}
WinExist("ahk_id " . hwnd_lfw) ; restore Last Found window
DetectHiddenWindows %prev_dhw%
; still running, force kill
if (this.Status == 0)
DllCall("TerminateProcess", "Ptr", this.__Handle, "UInt", exit_code)
}
class Pipe
{
__New(handle)
{
this.__Handle := handle + 0
}
__Delete()
{
this.Close()
}
Close()
{
try this._Stream.Close(), this._Stream := ""
DllCall("CloseHandle", "Ptr", this.__Handle)
}
_Stream := ""
Stream[]
{
get {
if (!this._Stream)
this._Stream := FileOpen(this.__Handle, "h")
return this._Stream
}
}
Encoding[]
{
get {
return this.Stream.Encoding
}
set {
return this.Stream.Encoding := value
}
}
}
class StreamReader extends Subprocess.Pipe
{
Read(chars*)
{
return this.Stream.Read(chars*)
}
ReadLine()
{
return this.Stream.ReadLine()
}
ReadAll()
{
all := ""
encoding := this.Encoding
VarSetCapacity(data, 4096)
while (read := this.Stream.RawRead(data, 4096))
NumPut(0, data, read, "UShort"), all .= StrGet(&data, read, encoding)
return all
}
RawRead(ByRef var_or_address, bytes)
{
return this.Stream.RawRead(var_or_address, bytes)
}
Peek(ByRef data:="", bytes:=4096, ByRef read:="", ByRef avail:="", ByRef left:="")
{
VarSetCapacity(data, bytes)
return DllCall("PeekNamedPipe", "Ptr", this.__Handle, "Ptr", &data
, "UInt", bytes, "UInt*", read, "UInt*", avail, "UInt*", left)
}
}
class StreamWriter extends Subprocess.Pipe
{
Write(string)
{
return this.Stream.Write(string)
}
WriteLine(string)
{
return this.Stream.WriteLine(string)
}
RawWrite(ByRef var_or_address, bytes)
{
return this.Stream.RawWrite(var_or_address, bytes)
}
}
}
Si lexikos niega el commit, obviamente ya arruina lo de arriba
. Lo unico que yo implemente la opcion de minificar scripts. Asi como lo tiene javascript y css, que limpian todo el file y hacen un output llamado "file.min.ext", en dado caso "file.min.ahk" con todo ya incluido para redistribution. Un script file un poco mas streamlined sin comentarios ni nada, por lo cual tmp file es el archivo minificado, y tengo no problema en que se genere.