2022-11-27 22:06:11 +00:00
-- A general architecture for free-wheeling, live programs:
-- on startup:
-- scan both the app directory and the save directory for files with numeric prefixes
new file-system format for freewheeling apps
1. No more version history, now we have just the contents of the current
version.
2. Editing a definition no longer changes the order in which definitions
load.
This should make repos easier to browse, and more amenable to modify.
You don't need driver.love anymore. And a stable order eliminates some
gotchas. For example:
using driver.love, define `Foo = 3` in a definition
define `Bar = Foo + 1`
edit and redefine `Foo = 4`
Before this commit, you'd get an error when you restart the app.
Definitions used to be loaded in version order, and editing a definition
would move it to the end of the load order, potentially after
definitions using it. I mostly avoided this by keeping top-level
definitions independent. It's fine to refer to any definition inside a
function body, we only need to be careful with initializers for global
variables which run immediately while loading.
After this commit you can still end up in a weird state if you modify a
definition that other later definitions use. In the above example, you
will now see Foo = 4 and Bar = 4. But when you restart, Foo = 4 and Bar
= 5. But that's no more confusing than Emacs's C-x C-e. It's still
a good idea to keep top-level definitions order-independent. It's just
confusing in a similar way to existing tools if you fail to do so. And
your tools won't tend to break as badly.
Why did I ever do my weird version history thing? I think it's my deep
aversion to risking losing any data entered. (Even though the app
currently will seem to lose data in those situations. You'd need to
leave your tools to find the data.) Now I rely on driver.love's undo to
avoid data loss, but once you shut it down you're stuck with what you
have on disk. Or in git.
I also wasn't aware for a long time of any primitives for deleting
files. This might have colored my choices a lot.
2023-04-16 19:15:03 +01:00
-- load files in order
2022-11-27 22:06:11 +00:00
--
-- then start drawing frames on screen and reacting to events
--
-- events from keyboard and mouse are handled as the app desires
--
new file-system format for freewheeling apps
1. No more version history, now we have just the contents of the current
version.
2. Editing a definition no longer changes the order in which definitions
load.
This should make repos easier to browse, and more amenable to modify.
You don't need driver.love anymore. And a stable order eliminates some
gotchas. For example:
using driver.love, define `Foo = 3` in a definition
define `Bar = Foo + 1`
edit and redefine `Foo = 4`
Before this commit, you'd get an error when you restart the app.
Definitions used to be loaded in version order, and editing a definition
would move it to the end of the load order, potentially after
definitions using it. I mostly avoided this by keeping top-level
definitions independent. It's fine to refer to any definition inside a
function body, we only need to be careful with initializers for global
variables which run immediately while loading.
After this commit you can still end up in a weird state if you modify a
definition that other later definitions use. In the above example, you
will now see Foo = 4 and Bar = 4. But when you restart, Foo = 4 and Bar
= 5. But that's no more confusing than Emacs's C-x C-e. It's still
a good idea to keep top-level definitions order-independent. It's just
confusing in a similar way to existing tools if you fail to do so. And
your tools won't tend to break as badly.
Why did I ever do my weird version history thing? I think it's my deep
aversion to risking losing any data entered. (Even though the app
currently will seem to lose data in those situations. You'd need to
leave your tools to find the data.) Now I rely on driver.love's undo to
avoid data loss, but once you shut it down you're stuck with what you
have on disk. Or in git.
I also wasn't aware for a long time of any primitives for deleting
files. This might have colored my choices a lot.
2023-04-16 19:15:03 +01:00
-- on incoming messages to a specific file, the app must:
-- determine the definition name from the first word
-- execute the value, returning any errors
-- look up the filename for the definition or define a new filename for it
-- save the message's value to the filename
2022-11-27 22:06:11 +00:00
--
2022-12-26 08:27:24 +00:00
-- if a game encounters a run-time error, send it to the driver and await
-- further instructions. The app will go unresponsive in the meantime, that
-- is expected. To shut it down cleanly, type C-q in the driver.
2022-11-27 22:06:11 +00:00
2023-09-09 18:47:22 +01:00
-- We try to save new definitions in the source directory, but this is not
-- possible if the app lives in a .love file. In that case new definitions
-- go in the save dir.
2022-11-27 22:06:11 +00:00
-- namespace for these functions
live = { }
-- state for these functions
Live = { }
-- a namespace of frameworky callbacks
-- these will be modified live
on = { }
new file-system format for freewheeling apps
1. No more version history, now we have just the contents of the current
version.
2. Editing a definition no longer changes the order in which definitions
load.
This should make repos easier to browse, and more amenable to modify.
You don't need driver.love anymore. And a stable order eliminates some
gotchas. For example:
using driver.love, define `Foo = 3` in a definition
define `Bar = Foo + 1`
edit and redefine `Foo = 4`
Before this commit, you'd get an error when you restart the app.
Definitions used to be loaded in version order, and editing a definition
would move it to the end of the load order, potentially after
definitions using it. I mostly avoided this by keeping top-level
definitions independent. It's fine to refer to any definition inside a
function body, we only need to be careful with initializers for global
variables which run immediately while loading.
After this commit you can still end up in a weird state if you modify a
definition that other later definitions use. In the above example, you
will now see Foo = 4 and Bar = 4. But when you restart, Foo = 4 and Bar
= 5. But that's no more confusing than Emacs's C-x C-e. It's still
a good idea to keep top-level definitions order-independent. It's just
confusing in a similar way to existing tools if you fail to do so. And
your tools won't tend to break as badly.
Why did I ever do my weird version history thing? I think it's my deep
aversion to risking losing any data entered. (Even though the app
currently will seem to lose data in those situations. You'd need to
leave your tools to find the data.) Now I rely on driver.love's undo to
avoid data loss, but once you shut it down you're stuck with what you
have on disk. Or in git.
I also wasn't aware for a long time of any primitives for deleting
files. This might have colored my choices a lot.
2023-04-16 19:15:03 +01:00
-- === on startup, load all files with numeric prefix
2022-11-27 22:06:11 +00:00
2023-11-17 06:10:03 +00:00
function live . load ( )
2023-11-17 16:31:41 +00:00
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
2023-04-16 18:46:40 +01:00
2022-11-27 22:06:11 +00:00
-- version control
new file-system format for freewheeling apps
1. No more version history, now we have just the contents of the current
version.
2. Editing a definition no longer changes the order in which definitions
load.
This should make repos easier to browse, and more amenable to modify.
You don't need driver.love anymore. And a stable order eliminates some
gotchas. For example:
using driver.love, define `Foo = 3` in a definition
define `Bar = Foo + 1`
edit and redefine `Foo = 4`
Before this commit, you'd get an error when you restart the app.
Definitions used to be loaded in version order, and editing a definition
would move it to the end of the load order, potentially after
definitions using it. I mostly avoided this by keeping top-level
definitions independent. It's fine to refer to any definition inside a
function body, we only need to be careful with initializers for global
variables which run immediately while loading.
After this commit you can still end up in a weird state if you modify a
definition that other later definitions use. In the above example, you
will now see Foo = 4 and Bar = 4. But when you restart, Foo = 4 and Bar
= 5. But that's no more confusing than Emacs's C-x C-e. It's still
a good idea to keep top-level definitions order-independent. It's just
confusing in a similar way to existing tools if you fail to do so. And
your tools won't tend to break as badly.
Why did I ever do my weird version history thing? I think it's my deep
aversion to risking losing any data entered. (Even though the app
currently will seem to lose data in those situations. You'd need to
leave your tools to find the data.) Now I rely on driver.love's undo to
avoid data loss, but once you shut it down you're stuck with what you
have on disk. Or in git.
I also wasn't aware for a long time of any primitives for deleting
files. This might have colored my choices a lot.
2023-04-16 19:15:03 +01:00
Live.filenames_to_load = { } -- filenames in order of numeric prefix
Live.filename = { } -- map from definition name to filename (including numeric prefix)
Live.final_prefix = 0
2022-11-27 22:06:11 +00:00
live.load_files_so_far ( )
new file-system format for freewheeling apps
1. No more version history, now we have just the contents of the current
version.
2. Editing a definition no longer changes the order in which definitions
load.
This should make repos easier to browse, and more amenable to modify.
You don't need driver.love anymore. And a stable order eliminates some
gotchas. For example:
using driver.love, define `Foo = 3` in a definition
define `Bar = Foo + 1`
edit and redefine `Foo = 4`
Before this commit, you'd get an error when you restart the app.
Definitions used to be loaded in version order, and editing a definition
would move it to the end of the load order, potentially after
definitions using it. I mostly avoided this by keeping top-level
definitions independent. It's fine to refer to any definition inside a
function body, we only need to be careful with initializers for global
variables which run immediately while loading.
After this commit you can still end up in a weird state if you modify a
definition that other later definitions use. In the above example, you
will now see Foo = 4 and Bar = 4. But when you restart, Foo = 4 and Bar
= 5. But that's no more confusing than Emacs's C-x C-e. It's still
a good idea to keep top-level definitions order-independent. It's just
confusing in a similar way to existing tools if you fail to do so. And
your tools won't tend to break as badly.
Why did I ever do my weird version history thing? I think it's my deep
aversion to risking losing any data entered. (Even though the app
currently will seem to lose data in those situations. You'd need to
leave your tools to find the data.) Now I rely on driver.love's undo to
avoid data loss, but once you shut it down you're stuck with what you
have on disk. Or in git.
I also wasn't aware for a long time of any primitives for deleting
files. This might have colored my choices a lot.
2023-04-16 19:15:03 +01:00
-- some hysteresis
2022-12-25 23:51:39 +00:00
Live.previous_read = 0
2022-11-27 22:06:11 +00:00
end
function live . load_files_so_far ( )
new file-system format for freewheeling apps
1. No more version history, now we have just the contents of the current
version.
2. Editing a definition no longer changes the order in which definitions
load.
This should make repos easier to browse, and more amenable to modify.
You don't need driver.love anymore. And a stable order eliminates some
gotchas. For example:
using driver.love, define `Foo = 3` in a definition
define `Bar = Foo + 1`
edit and redefine `Foo = 4`
Before this commit, you'd get an error when you restart the app.
Definitions used to be loaded in version order, and editing a definition
would move it to the end of the load order, potentially after
definitions using it. I mostly avoided this by keeping top-level
definitions independent. It's fine to refer to any definition inside a
function body, we only need to be careful with initializers for global
variables which run immediately while loading.
After this commit you can still end up in a weird state if you modify a
definition that other later definitions use. In the above example, you
will now see Foo = 4 and Bar = 4. But when you restart, Foo = 4 and Bar
= 5. But that's no more confusing than Emacs's C-x C-e. It's still
a good idea to keep top-level definitions order-independent. It's just
confusing in a similar way to existing tools if you fail to do so. And
your tools won't tend to break as badly.
Why did I ever do my weird version history thing? I think it's my deep
aversion to risking losing any data entered. (Even though the app
currently will seem to lose data in those situations. You'd need to
leave your tools to find the data.) Now I rely on driver.love's undo to
avoid data loss, but once you shut it down you're stuck with what you
have on disk. Or in git.
I also wasn't aware for a long time of any primitives for deleting
files. This might have colored my choices a lot.
2023-04-16 19:15:03 +01:00
for _ , filename in ipairs ( love.filesystem . getDirectoryItems ( ' ' ) ) do
2023-09-09 18:47:22 +01:00
local numeric_prefix , root = filename : match ( ' ^(%d+)-(.+) ' )
if numeric_prefix and tonumber ( numeric_prefix ) > 0 then -- skip 0000
Live.filename [ root ] = filename
table.insert ( Live.filenames_to_load , filename )
Live.final_prefix = math.max ( Live.final_prefix , tonumber ( numeric_prefix ) )
2022-11-27 22:06:11 +00:00
end
end
new file-system format for freewheeling apps
1. No more version history, now we have just the contents of the current
version.
2. Editing a definition no longer changes the order in which definitions
load.
This should make repos easier to browse, and more amenable to modify.
You don't need driver.love anymore. And a stable order eliminates some
gotchas. For example:
using driver.love, define `Foo = 3` in a definition
define `Bar = Foo + 1`
edit and redefine `Foo = 4`
Before this commit, you'd get an error when you restart the app.
Definitions used to be loaded in version order, and editing a definition
would move it to the end of the load order, potentially after
definitions using it. I mostly avoided this by keeping top-level
definitions independent. It's fine to refer to any definition inside a
function body, we only need to be careful with initializers for global
variables which run immediately while loading.
After this commit you can still end up in a weird state if you modify a
definition that other later definitions use. In the above example, you
will now see Foo = 4 and Bar = 4. But when you restart, Foo = 4 and Bar
= 5. But that's no more confusing than Emacs's C-x C-e. It's still
a good idea to keep top-level definitions order-independent. It's just
confusing in a similar way to existing tools if you fail to do so. And
your tools won't tend to break as badly.
Why did I ever do my weird version history thing? I think it's my deep
aversion to risking losing any data entered. (Even though the app
currently will seem to lose data in those situations. You'd need to
leave your tools to find the data.) Now I rely on driver.love's undo to
avoid data loss, but once you shut it down you're stuck with what you
have on disk. Or in git.
I also wasn't aware for a long time of any primitives for deleting
files. This might have colored my choices a lot.
2023-04-16 19:15:03 +01:00
table.sort ( Live.filenames_to_load )
2023-06-19 17:59:17 +01:00
-- load files from save dir
new file-system format for freewheeling apps
1. No more version history, now we have just the contents of the current
version.
2. Editing a definition no longer changes the order in which definitions
load.
This should make repos easier to browse, and more amenable to modify.
You don't need driver.love anymore. And a stable order eliminates some
gotchas. For example:
using driver.love, define `Foo = 3` in a definition
define `Bar = Foo + 1`
edit and redefine `Foo = 4`
Before this commit, you'd get an error when you restart the app.
Definitions used to be loaded in version order, and editing a definition
would move it to the end of the load order, potentially after
definitions using it. I mostly avoided this by keeping top-level
definitions independent. It's fine to refer to any definition inside a
function body, we only need to be careful with initializers for global
variables which run immediately while loading.
After this commit you can still end up in a weird state if you modify a
definition that other later definitions use. In the above example, you
will now see Foo = 4 and Bar = 4. But when you restart, Foo = 4 and Bar
= 5. But that's no more confusing than Emacs's C-x C-e. It's still
a good idea to keep top-level definitions order-independent. It's just
confusing in a similar way to existing tools if you fail to do so. And
your tools won't tend to break as badly.
Why did I ever do my weird version history thing? I think it's my deep
aversion to risking losing any data entered. (Even though the app
currently will seem to lose data in those situations. You'd need to
leave your tools to find the data.) Now I rely on driver.love's undo to
avoid data loss, but once you shut it down you're stuck with what you
have on disk. Or in git.
I also wasn't aware for a long time of any primitives for deleting
files. This might have colored my choices a lot.
2023-04-16 19:15:03 +01:00
for _ , filename in ipairs ( Live.filenames_to_load ) do
--? print('loading', filename)
2022-11-28 02:07:10 +00:00
local buf = love.filesystem . read ( filename )
assert ( buf and buf ~= ' ' )
2023-11-10 15:06:37 +00:00
local _ , definition_name = filename : match ( ' ^(%d+)-(.+) ' )
local status , err = live.eval ( buf , definition_name )
new file-system format for freewheeling apps
1. No more version history, now we have just the contents of the current
version.
2. Editing a definition no longer changes the order in which definitions
load.
This should make repos easier to browse, and more amenable to modify.
You don't need driver.love anymore. And a stable order eliminates some
gotchas. For example:
using driver.love, define `Foo = 3` in a definition
define `Bar = Foo + 1`
edit and redefine `Foo = 4`
Before this commit, you'd get an error when you restart the app.
Definitions used to be loaded in version order, and editing a definition
would move it to the end of the load order, potentially after
definitions using it. I mostly avoided this by keeping top-level
definitions independent. It's fine to refer to any definition inside a
function body, we only need to be careful with initializers for global
variables which run immediately while loading.
After this commit you can still end up in a weird state if you modify a
definition that other later definitions use. In the above example, you
will now see Foo = 4 and Bar = 4. But when you restart, Foo = 4 and Bar
= 5. But that's no more confusing than Emacs's C-x C-e. It's still
a good idea to keep top-level definitions order-independent. It's just
confusing in a similar way to existing tools if you fail to do so. And
your tools won't tend to break as badly.
Why did I ever do my weird version history thing? I think it's my deep
aversion to risking losing any data entered. (Even though the app
currently will seem to lose data in those situations. You'd need to
leave your tools to find the data.) Now I rely on driver.love's undo to
avoid data loss, but once you shut it down you're stuck with what you
have on disk. Or in git.
I also wasn't aware for a long time of any primitives for deleting
files. This might have colored my choices a lot.
2023-04-16 19:15:03 +01:00
if not status then
2023-06-19 17:56:24 +01:00
error ( err )
2023-01-08 04:27:38 +00:00
end
2022-11-28 02:07:10 +00:00
end
2022-11-27 22:06:11 +00:00
end
2023-01-03 02:21:55 +00:00
new file-system format for freewheeling apps
1. No more version history, now we have just the contents of the current
version.
2. Editing a definition no longer changes the order in which definitions
load.
This should make repos easier to browse, and more amenable to modify.
You don't need driver.love anymore. And a stable order eliminates some
gotchas. For example:
using driver.love, define `Foo = 3` in a definition
define `Bar = Foo + 1`
edit and redefine `Foo = 4`
Before this commit, you'd get an error when you restart the app.
Definitions used to be loaded in version order, and editing a definition
would move it to the end of the load order, potentially after
definitions using it. I mostly avoided this by keeping top-level
definitions independent. It's fine to refer to any definition inside a
function body, we only need to be careful with initializers for global
variables which run immediately while loading.
After this commit you can still end up in a weird state if you modify a
definition that other later definitions use. In the above example, you
will now see Foo = 4 and Bar = 4. But when you restart, Foo = 4 and Bar
= 5. But that's no more confusing than Emacs's C-x C-e. It's still
a good idea to keep top-level definitions order-independent. It's just
confusing in a similar way to existing tools if you fail to do so. And
your tools won't tend to break as badly.
Why did I ever do my weird version history thing? I think it's my deep
aversion to risking losing any data entered. (Even though the app
currently will seem to lose data in those situations. You'd need to
leave your tools to find the data.) Now I rely on driver.love's undo to
avoid data loss, but once you shut it down you're stuck with what you
have on disk. Or in git.
I also wasn't aware for a long time of any primitives for deleting
files. This might have colored my choices a lot.
2023-04-16 19:15:03 +01:00
APP = ' fw_app '
2022-11-27 22:06:11 +00:00
2023-04-16 18:42:27 +01:00
-- === on each frame, check for messages and alter the app as needed
2022-11-27 22:06:11 +00:00
function live . update ( dt )
2022-12-25 23:51:39 +00:00
if Current_time - Live.previous_read > 0.1 then
2022-12-26 08:27:24 +00:00
local buf = live.receive_from_driver ( )
2022-11-27 22:06:11 +00:00
if buf then
2023-11-17 07:12:14 +00:00
local possibly_mutated = live.run ( buf )
if possibly_mutated then
Mode = ' run '
2023-11-17 16:31:41 +00:00
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
2023-11-17 07:12:14 +00:00
end
2022-12-24 04:30:35 +00:00
if on.code_change then on.code_change ( ) end
2022-11-27 22:06:11 +00:00
end
2022-12-25 23:51:39 +00:00
Live.previous_read = Current_time
2022-11-27 22:06:11 +00:00
end
end
-- look for a message from outside, and return nil if there's nothing
2022-12-26 08:27:24 +00:00
function live . receive_from_driver ( )
2022-12-01 03:25:34 +00:00
local f = io.open ( love.filesystem . getAppdataDirectory ( ) .. ' /_love_akkartik_driver_app ' )
2022-11-27 22:06:11 +00:00
if f == nil then return nil end
local result = f : read ( ' *a ' )
f : close ( )
if result == ' ' then return nil end -- empty file == no message
2023-12-15 23:17:28 +00:00
print ( ' <= ' .. color_escape ( --[[bold]] 1 , --[[blue]] 4 ) )
2022-11-27 22:06:11 +00:00
print ( result )
print ( reset_terminal ( ) )
2023-02-05 05:19:05 +00:00
os.remove ( love.filesystem . getAppdataDirectory ( ) .. ' /_love_akkartik_driver_app ' )
2022-11-27 22:06:11 +00:00
return result
end
2022-12-26 08:27:24 +00:00
function live . send_to_driver ( msg )
2022-12-01 03:25:34 +00:00
local f = io.open ( love.filesystem . getAppdataDirectory ( ) .. ' /_love_akkartik_app_driver ' , ' w ' )
2022-11-27 22:06:11 +00:00
if f == nil then return end
f : write ( msg )
f : close ( )
2023-12-15 23:17:28 +00:00
print ( ' => ' .. color_escape ( 0 , --[[green]] 2 ) )
2022-11-27 22:06:11 +00:00
print ( msg )
print ( reset_terminal ( ) )
end
2023-01-08 03:11:50 +00:00
function live . send_run_time_error_to_driver ( msg )
2022-12-01 03:25:34 +00:00
local f = io.open ( love.filesystem . getAppdataDirectory ( ) .. ' /_love_akkartik_app_driver_run_time_error ' , ' w ' )
2022-11-27 22:27:04 +00:00
if f == nil then return end
f : write ( msg )
f : close ( )
2023-12-15 23:17:28 +00:00
print ( ' => ' .. color_escape ( 0 , --[[red]] 1 ) )
2022-11-27 22:27:04 +00:00
print ( msg )
2022-11-28 02:19:58 +00:00
print ( reset_terminal ( ) )
2022-11-27 22:27:04 +00:00
end
2022-11-27 22:06:11 +00:00
-- args:
-- format: 0 for normal, 1 for bold
-- color: 0-15
2023-12-15 23:17:28 +00:00
function color_escape ( format , color )
2022-11-27 22:06:11 +00:00
return ( ' \027 [%d;%dm ' ) : format ( format , 30 + color )
end
function reset_terminal ( )
return ' \027 [m '
end
2023-11-17 07:12:14 +00:00
-- returns true if we might have mutated the app, by either creating or deleting a definition
2022-11-27 22:06:11 +00:00
function live . run ( buf )
2022-12-26 08:27:24 +00:00
local cmd = live.get_cmd_from_buffer ( buf )
2022-11-27 22:06:11 +00:00
assert ( cmd )
print ( ' command is ' .. cmd )
if cmd == ' QUIT ' then
love.event . quit ( 1 )
2023-04-08 02:18:43 +01:00
elseif cmd == ' RESTART ' then
restart ( )
2022-11-27 22:06:11 +00:00
elseif cmd == ' MANIFEST ' then
new file-system format for freewheeling apps
1. No more version history, now we have just the contents of the current
version.
2. Editing a definition no longer changes the order in which definitions
load.
This should make repos easier to browse, and more amenable to modify.
You don't need driver.love anymore. And a stable order eliminates some
gotchas. For example:
using driver.love, define `Foo = 3` in a definition
define `Bar = Foo + 1`
edit and redefine `Foo = 4`
Before this commit, you'd get an error when you restart the app.
Definitions used to be loaded in version order, and editing a definition
would move it to the end of the load order, potentially after
definitions using it. I mostly avoided this by keeping top-level
definitions independent. It's fine to refer to any definition inside a
function body, we only need to be careful with initializers for global
variables which run immediately while loading.
After this commit you can still end up in a weird state if you modify a
definition that other later definitions use. In the above example, you
will now see Foo = 4 and Bar = 4. But when you restart, Foo = 4 and Bar
= 5. But that's no more confusing than Emacs's C-x C-e. It's still
a good idea to keep top-level definitions order-independent. It's just
confusing in a similar way to existing tools if you fail to do so. And
your tools won't tend to break as badly.
Why did I ever do my weird version history thing? I think it's my deep
aversion to risking losing any data entered. (Even though the app
currently will seem to lose data in those situations. You'd need to
leave your tools to find the data.) Now I rely on driver.love's undo to
avoid data loss, but once you shut it down you're stuck with what you
have on disk. Or in git.
I also wasn't aware for a long time of any primitives for deleting
files. This might have colored my choices a lot.
2023-04-16 19:15:03 +01:00
Live.filename [ APP ] = love.filesystem . getIdentity ( )
live.send_to_driver ( json.encode ( Live.filename ) )
2022-11-27 22:06:11 +00:00
elseif cmd == ' DELETE ' then
new file-system format for freewheeling apps
1. No more version history, now we have just the contents of the current
version.
2. Editing a definition no longer changes the order in which definitions
load.
This should make repos easier to browse, and more amenable to modify.
You don't need driver.love anymore. And a stable order eliminates some
gotchas. For example:
using driver.love, define `Foo = 3` in a definition
define `Bar = Foo + 1`
edit and redefine `Foo = 4`
Before this commit, you'd get an error when you restart the app.
Definitions used to be loaded in version order, and editing a definition
would move it to the end of the load order, potentially after
definitions using it. I mostly avoided this by keeping top-level
definitions independent. It's fine to refer to any definition inside a
function body, we only need to be careful with initializers for global
variables which run immediately while loading.
After this commit you can still end up in a weird state if you modify a
definition that other later definitions use. In the above example, you
will now see Foo = 4 and Bar = 4. But when you restart, Foo = 4 and Bar
= 5. But that's no more confusing than Emacs's C-x C-e. It's still
a good idea to keep top-level definitions order-independent. It's just
confusing in a similar way to existing tools if you fail to do so. And
your tools won't tend to break as badly.
Why did I ever do my weird version history thing? I think it's my deep
aversion to risking losing any data entered. (Even though the app
currently will seem to lose data in those situations. You'd need to
leave your tools to find the data.) Now I rely on driver.love's undo to
avoid data loss, but once you shut it down you're stuck with what you
have on disk. Or in git.
I also wasn't aware for a long time of any primitives for deleting
files. This might have colored my choices a lot.
2023-04-16 19:15:03 +01:00
local definition_name = buf : match ( ' ^%s*%S+%s+(%S+) ' )
2023-05-01 06:04:01 +01:00
if Live.frozen_definitions [ definition_name ] then
live.send_to_driver ( ' ERROR definition ' .. definition_name .. ' is part of Freewheeling infrastructure and cannot be deleted. ' )
return
end
new file-system format for freewheeling apps
1. No more version history, now we have just the contents of the current
version.
2. Editing a definition no longer changes the order in which definitions
load.
This should make repos easier to browse, and more amenable to modify.
You don't need driver.love anymore. And a stable order eliminates some
gotchas. For example:
using driver.love, define `Foo = 3` in a definition
define `Bar = Foo + 1`
edit and redefine `Foo = 4`
Before this commit, you'd get an error when you restart the app.
Definitions used to be loaded in version order, and editing a definition
would move it to the end of the load order, potentially after
definitions using it. I mostly avoided this by keeping top-level
definitions independent. It's fine to refer to any definition inside a
function body, we only need to be careful with initializers for global
variables which run immediately while loading.
After this commit you can still end up in a weird state if you modify a
definition that other later definitions use. In the above example, you
will now see Foo = 4 and Bar = 4. But when you restart, Foo = 4 and Bar
= 5. But that's no more confusing than Emacs's C-x C-e. It's still
a good idea to keep top-level definitions order-independent. It's just
confusing in a similar way to existing tools if you fail to do so. And
your tools won't tend to break as badly.
Why did I ever do my weird version history thing? I think it's my deep
aversion to risking losing any data entered. (Even though the app
currently will seem to lose data in those situations. You'd need to
leave your tools to find the data.) Now I rely on driver.love's undo to
avoid data loss, but once you shut it down you're stuck with what you
have on disk. Or in git.
I also wasn't aware for a long time of any primitives for deleting
files. This might have colored my choices a lot.
2023-04-16 19:15:03 +01:00
if Live.filename [ definition_name ] then
local index = table.find ( Live.filenames_to_load , Live.filename [ definition_name ] )
table.remove ( Live.filenames_to_load , index )
2023-11-10 01:53:30 +00:00
live.eval ( definition_name .. ' = nil ' , ' driver ' ) -- ignore errors which will likely be from keywords like `function = nil`
2023-09-09 18:47:22 +01:00
-- try to remove the file from both source_dir and save_dir
-- this won't work for files inside .love files
2023-12-18 19:41:29 +00:00
nativefs.remove ( App.source_dir .. Live.filename [ definition_name ] )
new file-system format for freewheeling apps
1. No more version history, now we have just the contents of the current
version.
2. Editing a definition no longer changes the order in which definitions
load.
This should make repos easier to browse, and more amenable to modify.
You don't need driver.love anymore. And a stable order eliminates some
gotchas. For example:
using driver.love, define `Foo = 3` in a definition
define `Bar = Foo + 1`
edit and redefine `Foo = 4`
Before this commit, you'd get an error when you restart the app.
Definitions used to be loaded in version order, and editing a definition
would move it to the end of the load order, potentially after
definitions using it. I mostly avoided this by keeping top-level
definitions independent. It's fine to refer to any definition inside a
function body, we only need to be careful with initializers for global
variables which run immediately while loading.
After this commit you can still end up in a weird state if you modify a
definition that other later definitions use. In the above example, you
will now see Foo = 4 and Bar = 4. But when you restart, Foo = 4 and Bar
= 5. But that's no more confusing than Emacs's C-x C-e. It's still
a good idea to keep top-level definitions order-independent. It's just
confusing in a similar way to existing tools if you fail to do so. And
your tools won't tend to break as badly.
Why did I ever do my weird version history thing? I think it's my deep
aversion to risking losing any data entered. (Even though the app
currently will seem to lose data in those situations. You'd need to
leave your tools to find the data.) Now I rely on driver.love's undo to
avoid data loss, but once you shut it down you're stuck with what you
have on disk. Or in git.
I also wasn't aware for a long time of any primitives for deleting
files. This might have colored my choices a lot.
2023-04-16 19:15:03 +01:00
love.filesystem . remove ( Live.filename [ definition_name ] )
Live.filename [ definition_name ] = nil
end
2023-05-01 06:58:31 +01:00
live.send_to_driver ( ' {} ' )
2023-11-17 07:12:14 +00:00
return true
2022-11-27 22:06:11 +00:00
elseif cmd == ' GET ' then
new file-system format for freewheeling apps
1. No more version history, now we have just the contents of the current
version.
2. Editing a definition no longer changes the order in which definitions
load.
This should make repos easier to browse, and more amenable to modify.
You don't need driver.love anymore. And a stable order eliminates some
gotchas. For example:
using driver.love, define `Foo = 3` in a definition
define `Bar = Foo + 1`
edit and redefine `Foo = 4`
Before this commit, you'd get an error when you restart the app.
Definitions used to be loaded in version order, and editing a definition
would move it to the end of the load order, potentially after
definitions using it. I mostly avoided this by keeping top-level
definitions independent. It's fine to refer to any definition inside a
function body, we only need to be careful with initializers for global
variables which run immediately while loading.
After this commit you can still end up in a weird state if you modify a
definition that other later definitions use. In the above example, you
will now see Foo = 4 and Bar = 4. But when you restart, Foo = 4 and Bar
= 5. But that's no more confusing than Emacs's C-x C-e. It's still
a good idea to keep top-level definitions order-independent. It's just
confusing in a similar way to existing tools if you fail to do so. And
your tools won't tend to break as badly.
Why did I ever do my weird version history thing? I think it's my deep
aversion to risking losing any data entered. (Even though the app
currently will seem to lose data in those situations. You'd need to
leave your tools to find the data.) Now I rely on driver.love's undo to
avoid data loss, but once you shut it down you're stuck with what you
have on disk. Or in git.
I also wasn't aware for a long time of any primitives for deleting
files. This might have colored my choices a lot.
2023-04-16 19:15:03 +01:00
local definition_name = buf : match ( ' ^%s*%S+%s+(%S+) ' )
2023-04-10 06:31:12 +01:00
local val , _ = live.get_binding ( definition_name )
2023-01-08 04:39:52 +00:00
if val then
live.send_to_driver ( val )
else
live.send_to_driver ( ' ERROR no such value ' )
end
2023-01-07 22:43:38 +00:00
elseif cmd == ' GET* ' then
-- batch version of GET
local result = { }
2023-04-10 06:31:12 +01:00
for definition_name in buf : gmatch ( ' %s+(%S+) ' ) do
print ( definition_name )
local val , _ = live.get_binding ( definition_name )
2023-01-08 04:48:22 +00:00
if val then
table.insert ( result , val )
end
2023-01-07 22:43:38 +00:00
end
2023-01-08 05:23:51 +00:00
local delimiter = ' \n ==fw: definition boundary== \n '
live.send_to_driver ( table.concat ( result , delimiter ) .. delimiter ) -- send a final delimiter to simplify the driver's task
2023-01-26 02:03:00 +00:00
elseif cmd == ' DEFAULT_MAP ' then
local contents = love.filesystem . read ( ' default_map ' )
if contents == nil then contents = ' {} ' end
live.send_to_driver ( contents )
2022-11-27 22:06:11 +00:00
-- other commands go here
else
2023-07-24 08:11:33 +01:00
local definition_name = live.get_definition_name_from_buffer ( buf )
2023-07-24 08:40:34 +01:00
if definition_name == nil then
-- contents are all Lua comments; we don't currently have a plan for them
live.send_to_driver ( ' ERROR empty definition ' )
return
end
2023-07-24 08:11:33 +01:00
print ( ' definition name is ' .. definition_name )
2023-04-10 06:31:12 +01:00
if Live.frozen_definitions [ definition_name ] then
live.send_to_driver ( ' ERROR definition ' .. definition_name .. ' is part of Freewheeling infrastructure and cannot be safely edited live. ' )
2023-04-10 06:29:44 +01:00
return
end
2023-11-10 01:53:30 +00:00
local status , err = live.eval ( buf , definition_name )
2022-11-27 22:06:11 +00:00
if not status then
-- throw an error
2023-11-10 18:15:35 +00:00
live.send_to_driver ( ' ERROR ' .. cleaned_up_frame ( tostring ( err ) ) )
2023-01-08 04:28:37 +00:00
return
2022-11-27 22:06:11 +00:00
end
new file-system format for freewheeling apps
1. No more version history, now we have just the contents of the current
version.
2. Editing a definition no longer changes the order in which definitions
load.
This should make repos easier to browse, and more amenable to modify.
You don't need driver.love anymore. And a stable order eliminates some
gotchas. For example:
using driver.love, define `Foo = 3` in a definition
define `Bar = Foo + 1`
edit and redefine `Foo = 4`
Before this commit, you'd get an error when you restart the app.
Definitions used to be loaded in version order, and editing a definition
would move it to the end of the load order, potentially after
definitions using it. I mostly avoided this by keeping top-level
definitions independent. It's fine to refer to any definition inside a
function body, we only need to be careful with initializers for global
variables which run immediately while loading.
After this commit you can still end up in a weird state if you modify a
definition that other later definitions use. In the above example, you
will now see Foo = 4 and Bar = 4. But when you restart, Foo = 4 and Bar
= 5. But that's no more confusing than Emacs's C-x C-e. It's still
a good idea to keep top-level definitions order-independent. It's just
confusing in a similar way to existing tools if you fail to do so. And
your tools won't tend to break as badly.
Why did I ever do my weird version history thing? I think it's my deep
aversion to risking losing any data entered. (Even though the app
currently will seem to lose data in those situations. You'd need to
leave your tools to find the data.) Now I rely on driver.love's undo to
avoid data loss, but once you shut it down you're stuck with what you
have on disk. Or in git.
I also wasn't aware for a long time of any primitives for deleting
files. This might have colored my choices a lot.
2023-04-16 19:15:03 +01:00
-- eval succeeded without errors; persist the definition
local filename = Live.filename [ definition_name ]
if filename == nil then
Live.final_prefix = Live.final_prefix + 1
filename = ( ' %04d-%s ' ) : format ( Live.final_prefix , definition_name )
table.insert ( Live.filenames_to_load , filename )
Live.filename [ definition_name ] = filename
end
2023-09-09 18:47:22 +01:00
-- try to write to source dir
2023-12-18 19:41:29 +00:00
local status , err = nativefs.write ( App.source_dir .. filename , buf )
2023-09-09 18:47:22 +01:00
if err then
2023-12-18 19:41:29 +00:00
-- fall back on save dir
2023-11-17 20:28:45 +00:00
local status , err2 = love.filesystem . write ( filename , buf )
2023-09-09 18:47:22 +01:00
if err2 then
-- throw an error
live.send_to_driver ( ' ERROR ' .. tostring ( err .. ' \n \n ' .. err2 ) )
2023-11-17 07:12:14 +00:00
return true
2023-09-09 18:47:22 +01:00
end
end
2023-01-23 07:45:44 +00:00
-- run all tests
2023-01-23 00:39:51 +00:00
Test_errors = { }
2023-01-24 04:12:23 +00:00
App.run_tests ( record_error_by_test )
2023-01-23 17:16:31 +00:00
live.send_to_driver ( json.encode ( Test_errors ) )
2023-11-17 07:12:14 +00:00
return true
2023-01-23 07:45:44 +00:00
end
end
2022-12-26 08:27:24 +00:00
function live . get_cmd_from_buffer ( buf )
2023-07-24 08:44:34 +01:00
-- return the first word
2023-07-24 08:11:33 +01:00
return buf : match ( ' ^%s*(%S+) ' )
end
function live . get_definition_name_from_buffer ( buf )
2023-07-25 23:34:44 +01:00
return first_noncomment_word ( buf )
end
-- return the first word (separated by whitespace) that's not in a Lua comment
-- or empty string if there's nothing
-- ignore strings; we don't expect them to be the first word in a program
function first_noncomment_word ( str )
local pos = 1
while pos <= # str do -- not Unicode-aware; hopefully it doesn't need to be
if str : sub ( pos , pos ) == ' - ' then
-- skip any comments
if str : sub ( pos + 1 , pos + 1 ) == ' - ' then
-- definitely start of a comment
local long_comment_header = str : match ( ' ^%[=*%[ ' , pos + 2 )
if long_comment_header then
-- long comment
local long_comment_trailer = long_comment_header : gsub ( ' %[ ' , ' ] ' )
pos = str : find ( long_comment_trailer , pos , --[[plain]] true )
if pos == nil then return ' ' end -- incomplete comment; no first word
pos = pos + # long_comment_trailer
else
-- line comment
pos = str : find ( ' \n ' , pos )
if pos == nil then return ' ' end -- incomplete comment; no first word
end
end
end
-- any non-whitespace that's not a comment is the first word
if str : sub ( pos , pos ) : match ( ' %s ' ) then
pos = pos + 1
else
return str : match ( ' ^%S* ' , pos )
end
2023-07-24 08:44:34 +01:00
end
2023-07-25 23:34:44 +01:00
return ' '
end
function test_first_noncomment_word ( )
check_eq ( first_noncomment_word ( ' ' ) , ' ' , ' empty string ' )
check_eq ( first_noncomment_word ( ' abc ' ) , ' abc ' , ' single word ' )
check_eq ( first_noncomment_word ( ' abc def ' ) , ' abc ' , ' stop at space ' )
check_eq ( first_noncomment_word ( ' abc \t def ' ) , ' abc ' , ' stop at tab ' )
check_eq ( first_noncomment_word ( ' abc \n def ' ) , ' abc ' , ' stop at newline ' )
check_eq ( first_noncomment_word ( ' -- abc \n def ' ) , ' def ' , ' ignore line comment ' )
check_eq ( first_noncomment_word ( ' --[[abc]] def ' ) , ' def ' , ' ignore block comment ' )
check_eq ( first_noncomment_word ( ' --[[abc \n ]] def ' ) , ' def ' , ' ignore multi-line block comment ' )
check_eq ( first_noncomment_word ( ' --[[abc \n --]] def ' ) , ' def ' , ' ignore comment leader before block comment trailer ' )
check_eq ( first_noncomment_word ( ' --[=[abc]=] def ' ) , ' def ' , ' ignore long comment ' )
check_eq ( first_noncomment_word ( ' --[=[abc]] def ]=] ghi ' ) , ' ghi ' , ' ignore long comment containing block comment trailer ' )
check_eq ( first_noncomment_word ( ' --[===[abc \n \n def ghi \n jkl]===]mno \n pqr ' ) , ' mno ' , ' ignore long comment containing block comment trailer ' )
check_eq ( first_noncomment_word ( ' - ' ) , ' - ' , ' incomplete comment token ' )
check_eq ( first_noncomment_word ( ' --abc ' ) , ' ' , ' incomplete line comment ' )
check_eq ( first_noncomment_word ( ' --abc \n ' ) , ' ' , ' just a line comment ' )
check_eq ( first_noncomment_word ( ' --abc \n ' ) , ' ' , ' just a line comment 2 ' )
check_eq ( first_noncomment_word ( ' --[ab \n ' ) , ' ' , ' incomplete block comment token is a line comment ' )
check_eq ( first_noncomment_word ( ' --[[ab ' ) , ' ' , ' incomplete block comment ' )
check_eq ( first_noncomment_word ( ' --[[ab \n ] ' ) , ' ' , ' incomplete block comment 2 ' )
check_eq ( first_noncomment_word ( ' --[=[ab \n ]] ]= ' ) , ' ' , ' incomplete block comment 3 ' )
check_eq ( first_noncomment_word ( ' --[=[ab \n ]] ]=] ' ) , ' ' , ' just a block comment ' )
check_eq ( first_noncomment_word ( ' --[=[ab \n ]] ]=] \n \n ' ) , ' ' , ' just a block comment 2 ' )
2022-12-26 08:27:24 +00:00
end
2022-11-27 22:06:11 +00:00
function live . get_binding ( name )
new file-system format for freewheeling apps
1. No more version history, now we have just the contents of the current
version.
2. Editing a definition no longer changes the order in which definitions
load.
This should make repos easier to browse, and more amenable to modify.
You don't need driver.love anymore. And a stable order eliminates some
gotchas. For example:
using driver.love, define `Foo = 3` in a definition
define `Bar = Foo + 1`
edit and redefine `Foo = 4`
Before this commit, you'd get an error when you restart the app.
Definitions used to be loaded in version order, and editing a definition
would move it to the end of the load order, potentially after
definitions using it. I mostly avoided this by keeping top-level
definitions independent. It's fine to refer to any definition inside a
function body, we only need to be careful with initializers for global
variables which run immediately while loading.
After this commit you can still end up in a weird state if you modify a
definition that other later definitions use. In the above example, you
will now see Foo = 4 and Bar = 4. But when you restart, Foo = 4 and Bar
= 5. But that's no more confusing than Emacs's C-x C-e. It's still
a good idea to keep top-level definitions order-independent. It's just
confusing in a similar way to existing tools if you fail to do so. And
your tools won't tend to break as badly.
Why did I ever do my weird version history thing? I think it's my deep
aversion to risking losing any data entered. (Even though the app
currently will seem to lose data in those situations. You'd need to
leave your tools to find the data.) Now I rely on driver.love's undo to
avoid data loss, but once you shut it down you're stuck with what you
have on disk. Or in git.
I also wasn't aware for a long time of any primitives for deleting
files. This might have colored my choices a lot.
2023-04-16 19:15:03 +01:00
if Live.filename [ name ] then
return love.filesystem . read ( Live.filename [ name ] )
2022-11-27 22:06:11 +00:00
end
end
2023-04-18 05:10:17 +01:00
function table . find ( h , x )
for k , v in pairs ( h ) do
if v == x then
return k
end
end
end
2022-11-27 22:06:11 +00:00
-- Wrapper for Lua's weird evaluation model.
-- Lua is persnickety about expressions vs statements, so we need to do some
-- extra work to get the result of an evaluation.
2023-11-10 01:53:30 +00:00
-- filename will show up in call stacks for any error messages
2022-11-27 22:06:11 +00:00
-- return values:
-- all well -> true, ...
-- load failed -> nil, error message
2023-04-15 18:12:32 +01:00
-- run (pcall) failed -> false, error message
2023-11-10 01:53:30 +00:00
function live . eval ( buf , filename )
2022-11-27 22:06:11 +00:00
-- We assume a program is either correct with 'return' prefixed xor not.
-- Is this correct? Who knows! But the Lua REPL does this as well.
2023-11-10 02:03:29 +00:00
local f = load ( ' return ' .. buf , filename or ' REPL ' )
2022-11-27 22:06:11 +00:00
if f then
return pcall ( f )
end
2023-11-10 01:53:30 +00:00
local f , err = load ( buf , filename or ' REPL ' )
2022-11-27 22:06:11 +00:00
if f then
return pcall ( f )
else
return nil , err
end
end
2023-04-16 18:42:27 +01:00
-- === infrastructure for performing safety checks on any new definition
2023-04-15 18:14:42 +01:00
-- Everything that exists before we start loading the live files is frozen and
-- can't be edited live.
function live . freeze_all_existing_definitions ( )
Live.frozen_definitions = { on = true } -- special case for version 1
local done = { }
done [ Live.frozen_definitions ] = true
live.freeze_all_existing_definitions_in ( _G , { } , done )
end
function live . freeze_all_existing_definitions_in ( tab , scopes , done )
-- track duplicates to avoid cycles like _G._G, _G._G._G, etc.
if done [ tab ] then return end
done [ tab ] = true
for name , binding in pairs ( tab ) do
local full_name = live.full_name ( scopes , name )
--? print(full_name)
Live.frozen_definitions [ full_name ] = true
if type ( binding ) == ' table ' and full_name ~= ' package ' then -- var 'package' contains copies of all modules, but not the best name; rely on people to not modify package.loaded.io.open, etc.
table.insert ( scopes , name )
live.freeze_all_existing_definitions_in ( binding , scopes , done )
table.remove ( scopes )
end
end
end
function live . full_name ( scopes , name )
local ns = table.concat ( scopes , ' . ' )
if # ns == 0 then return name end
return ns .. ' . ' .. name
end
-- === on error, pause the app and wait for messages
2022-11-27 22:06:11 +00:00
-- return nil to continue the event loop, non-nil to quit
function live . handle_error ( err )
2023-11-22 06:02:24 +00:00
love.graphics . setCanvas ( ) -- undo any canvas we happened to be within, otherwise LÖVE seizes up
2023-04-20 08:30:25 +01:00
Mode = ' error '
2023-11-10 18:15:35 +00:00
local callstack = debug.traceback ( ' ' , --[[stack frame]] 2 )
local cleaned_up_error = ' Error: ' .. cleaned_up_frame ( tostring ( err ) ) .. ' \n ' .. cleaned_up_callstack ( callstack )
live.send_run_time_error_to_driver ( cleaned_up_error )
Error_message = ' Something is wrong. Sorry! \n \n ' .. cleaned_up_error .. ' \n \n ' ..
2023-04-20 08:30:25 +01:00
" (Note: function names above don't include outer tables. So functions like on.draw might show up as just 'draw', etc.) \n \n " ..
' Options: \n ' ..
' - press "ctrl+c" (without the quotes) to copy this message to your clipboard to send to me: ak@akkartik.com \n ' ..
' - press any other key to retry, see if things start working again \n ' ..
' - run driver.love to try to fix it yourself. As you do, feel free to ask me questions: ak@akkartik.com \n '
Error_count = Error_count + 1
if Error_count > 1 then
Error_message = Error_message .. ( ' \n \n This is error #%d in this session; things will probably not improve in this session. Please copy the message and send it to me: ak@akkartik.com. ' ) : format ( Error_count )
2022-11-27 22:06:11 +00:00
end
2023-04-20 08:30:25 +01:00
print ( Error_message )
2022-11-27 22:06:11 +00:00
end
2023-11-10 18:15:35 +00:00
2023-11-17 16:31:41 +00:00
function live . handle_initialization_error ( err )
Redo_initialization = true
live.handle_error ( err )
end
2023-11-10 18:15:35 +00:00
-- 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"]
-- This function cleans out this cruft from error callstacks.
-- It also strips out the numeric prefixes we introduce in filenames.
function cleaned_up_callstack ( callstack )
local frames = { }
for frame in string.gmatch ( callstack , ' [^ \n ]+ \n * ' ) do
table.insert ( frames , cleaned_up_frame ( frame ) )
end
-- the initial "stack traceback:" line was unindented and remains so
return table.concat ( frames , ' \n \t ' )
end
function cleaned_up_frame ( frame )
local line = frame : gsub ( ' ^%s*(.-) \n ?$ ' , ' %1 ' )
local filename , rest = line : match ( ' ([^:]*):(.*) ' )
return cleaned_up_filename ( filename ) .. ' : ' .. rest
end
function cleaned_up_filename ( filename )
-- pass through frames that don't match this format
-- this includes the initial line "stack traceback:"
local core_filename = filename : match ( ' ^%[string "(.*)"%]$ ' )
if core_filename == nil then return filename end
-- strip out the numeric prefixes we introduce in filenames
local _ , core_filename2 = core_filename : match ( ' ^(%d+)-(.+) ' )
return core_filename2 or core_filename
end