Merge luaML.love

This commit is contained in:
Kartik K. Agaram 2023-11-17 16:21:42 -08:00
commit 912506614c
4 changed files with 135 additions and 23 deletions

View File

@ -154,6 +154,94 @@ robust to apps changing out from under it.
```
Hit F4. The error disappears.
Driver can connect to app on errors in on.initialize (and `on.load_settings`).
* clone this repo to a new client app, clear its save dir, run it, run the
driver, define `on.initialize` with a run-time error:
```
on.initialize = function()
foo = bar+1
end
```
Hit F4.
Quit the client app and restart. App shows an error.
Edit `on.initialize` in the driver and remove the error:
```
on.initialize = function()
end
```
Hit F4. The error disappears from the app and driver.
* clone this repo to a new client app, clear its save dir, run it, run the
driver, define `on.initialize` with a run-time error:
```
on.initialize = function()
foo = bar+1
end
```
Hit F4.
Quit the client app and restart. App shows an error.
Hit F4 again in the driver (without fixing the error).
The client app continues to show the error.
Driver can connect to app on errors in `on.quit` (and `on.save_settings`).
* clone this repo to a new client app, clear its save dir, run it, run the
driver, define `on.quit` with a run-time error:
```
on.quit = function()
foo = bar+1
end
```
Hit F4.
Try to quit the client app. It shows an error and refuses to quit.
Edit `on.quit` in the driver and remove the error:
```
on.quit = function()
end
```
Hit F4. The error disappears from the app and driver.
Try to quit the client app. Now the quit succeeds.
* clone this repo to a new client app, clear its save dir, run it, run the
driver, define `on.quit` with a run-time error:
```
on.quit = function()
foo = bar+1
end
```
Hit F4.
Try to quit the client app. It shows an error and refuses to quit.
Hit F4 again in the driver (without fixing the error).
Try to quit the client app again. It continues to show the error.
Driver can connect to app that contains test failures on startup.
* clone this repo to a new client app, clear its save dir, run it, run the
driver, define a new test with an invalid assertion:
```
test_foo = function()
check(nil, 'foo')
end
```
Hit F4. The test fails.
Quit the client app and restart. App shows an error.
Edit `test_foo` in the driver and remove the error:
```
test_foo = function()
end
```
Hit F4. The error disappears from the app and driver.
* clone this repo to a new client app, clear its save dir, run it, run the
driver, define a new test with an invalid assertion:
```
test_foo = function()
check(nil, 'foo')
end
```
Hit F4.
Quit the client app and restart. App shows an error.
Hit F4 again in the driver (without fixing the error).
The client app continues to show the error.
* clone this repo to a new client app, clear its save dir, run it, run the
driver, add a definition containing invalid Lua:
```

28
app.lua
View File

@ -14,6 +14,7 @@ local Keys_down = {}
function love.run()
App.version_check()
App.snapshot_love()
live.load()
-- have LÖVE delegate all handlers to App if they exist
for name in pairs(love.handlers) do
if App[name] then
@ -35,15 +36,17 @@ function love.run()
end
end
-- Stash current state of App for tests
App_for_tests = {}
for k,v in pairs(App) do
App_for_tests[k] = v
end
-- there's one nested table
App_for_tests.screen = {}
for k,v in pairs(App.screen) do
App_for_tests.screen[k] = v
-- Stash initial state of App (right after loading files) for tests
if App_for_tests == nil then
App_for_tests = {}
for k,v in pairs(App) do
App_for_tests[k] = v
end
-- there's one nested table
App_for_tests.screen = {}
for k,v in pairs(App.screen) do
App_for_tests.screen[k] = v
end
end
-- Mutate App for the real app
-- disable test methods
@ -136,11 +139,14 @@ function love.run()
App.run_tests(record_error)
-- example handler
if #Test_errors > 0 then
error('There were test failures:\n\n'..table.concat(Test_errors, '\n'))
Mode = 'error'
Redo_initialization = true
Error_message = 'There were test failures:\n\n'..table.concat(Test_errors, '\n')
live.send_run_time_error_to_driver(Error_message)
end
App.initialize_globals()
App.initialize(love.arg.parseGameArguments(arg), arg)
xpcall(function() App.initialize(love.arg.parseGameArguments(arg), arg) end, live.handle_initialization_error)
love.timer.step()
local dt = 0

View File

@ -32,8 +32,10 @@ on = {}
-- === on startup, load all files with numeric prefix
function live.initialize(arg)
live.freeze_all_existing_definitions()
function live.load()
if Live.frozen_definitions == nil then -- a second run due to initialization errors will contain definitions we don't want to freeze
live.freeze_all_existing_definitions()
end
-- version control
Live.filenames_to_load = {} -- filenames in order of numeric prefix
@ -44,8 +46,6 @@ function live.initialize(arg)
-- some hysteresis
Live.previous_read = 0
Live.previous_run_time_error_check_time = 0
if on.load then on.load() end
end
function live.load_files_so_far()
@ -80,8 +80,15 @@ function live.update(dt)
if Current_time - Live.previous_read > 0.1 then
local buf = live.receive_from_driver()
if buf then
live.run(buf)
Mode = 'run'
local possibly_mutated = live.run(buf)
if possibly_mutated then
Mode = 'run'
if Redo_initialization then
Redo_initialization = nil
love.run() -- won't actually replace the event loop;
-- we're just running it for its initialization side-effects
end
end
if on.code_change then on.code_change() end
end
Live.previous_read = Current_time
@ -137,7 +144,7 @@ function reset_terminal()
return '\027[m'
end
-- define or undefine top-level bindings
-- returns true if we might have mutated the app, by either creating or deleting a definition
function live.run(buf)
local cmd = live.get_cmd_from_buffer(buf)
assert(cmd)
@ -166,6 +173,7 @@ function live.run(buf)
Live.filename[definition_name] = nil
end
live.send_to_driver('{}')
return true
elseif cmd == 'GET' then
local definition_name = buf:match('^%s*%S+%s+(%S+)')
local val, _ = live.get_binding(definition_name)
@ -222,17 +230,18 @@ function live.run(buf)
if err then
-- not possible; perhaps it's a .love file
-- try to write to save dir
local status, err2 = App.write_file(App.save_dir..filename, buf)
local status, err2 = love.filesystem.write(filename, buf)
if err2 then
-- throw an error
live.send_to_driver('ERROR '..tostring(err..'\n\n'..err2))
return
return true
end
end
-- run all tests
Test_errors = {}
App.run_tests(record_error_by_test)
live.send_to_driver(json.encode(Test_errors))
return true
end
end
@ -395,6 +404,11 @@ function live.handle_error(err)
print(Error_message)
end
function live.handle_initialization_error(err)
Redo_initialization = true
live.handle_error(err)
end
-- I tend to read code from files myself (say using love.filesystem calls)
-- rather than offload that to load().
-- Functions compiled in this manner have ugly filenames of the form [string "filename"]

View File

@ -76,8 +76,6 @@ function App.initialize(arg)
love.graphics.setBackgroundColor(1,1,1)
HUD_font = love.graphics.newFont(HUD_font_height)
live.initialize(arg)
if love.filesystem.getInfo('config') then
load_settings()
else
@ -264,7 +262,13 @@ end
function App.keyreleased(key, scancode)
if Mode == 'error' then
Mode = 'run'
if Redo_initialization then
Redo_initialization = nil
love.run() -- won't actually replace the event loop;
-- we're just running it for its initialization side-effects
else
Mode = 'run'
end
return
end
-- ignore events for some time after window in focus (mostly alt-tab)