Godot: directory recursive function stackoverflow - directory

The following function prints the directories and files within a path, I want it to go into the directories and sub directories by using a recursive function. This causes a stackoverflow error. It only works if doesn't call "RecursiveSearch" func but only prints out directories and files but not subdirectories.
extends Node
func RecursiveSearch(dir):
var path = ("res://")
var err = dir.open(path)
if err != OK:
print("error occurred")
return
dir.list_dir_begin() # points to the first one, true ignores special directory entries (.) and (..)
var name = dir.get_next() # retrieves the firdt file or dir
while name != "":
if dir.current_is_dir(): # test if it's a dir type
print("dir : " , name)
RecursiveSearch(dir)
elif !dir.current_is_dir():
print("file : " , name)
else:
break
name = dir.get_next() # points to the next dir or file
dir.list_dir_end()
func _ready():
var dir = Directory.new()
RecursiveSearch(dir)

Directory.list_dir_begin() only lists the immediate children of a directory. To recurse into a subdirectory, you will need to create a new Directory object for the subdirectory, and call your RecursiveSearch on that.
As it stands, your function calls itself with its own argument. It will do the exact same thing--call itself with the same argument--again and again until it hits the recursion limit.

Related

Clean language: append number in end of file, fwritei doesn't work

I'm trying to write function that receive [String] which are names of files, String which is the name of the files directory and *f. The function will append to each file an integer in the end.
Here is what I got so far:
import StdEnv
import StdFile
import FileManipulation
appendNumInEndOfVmFiles :: [String] String *f -> String
appendNumInEndOfVmFiles [] dirname w = "finished"
appendNumInEndOfVmFiles [x:xs] dirname w
# path = dirname +++ "\\\\" +++ x
# (ok,file,files) = fopen path FAppendText w
# file = fwritei 12 file
# (ok2,_) = fclose file w
= appendNumInEndOfVmFiles xs dirname w
Start w
// 1. Receive name of directory from the user.
# (io,w) = stdio w // open stdio
# io = fwrites "Enter name of directory:\n" io // ask for name
# (name,io) = freadline io // read in name
# name = name % (0, size name - 2) // remove \n from name
# (ok,w) = fclose io w // close stdio
| not ok = abort "Couldn't close stdio" // abort in case of failure
// 2. Get a list of all file names in that directory.
# (dir,w) = getDirectoryContents (RelativePath [PathDown name]) w
# fileList = getNamesOfFilesInDirectory (getEntriesList dir)
= appendNumInEndOfVmFiles (getVmFiles fileList) name w
Assume that getVmFiles is defined in my FileManipulation.dcl file and in the context of this problem name is "myDir" and file list is ["hello.vm","Wiki.vm"]
For some reason, even that I got "finished" message on the screen, the files aren't modified. No matter what kind of integer I give to fopen, even if its FWriteText or FWriteData its still doing nothing... also even if I'm using fwritec or fwrites with characters nothing happened.
What I'm missing here? Thanks a lot!
For some reason, even that I got "finished" message on the screen, the files aren't modified.
This is due to lazy evaluation. In appendNumInEndOfVmFiles, the result of fclose is not used, so fclose is not evaluated. Because of this, fwritei does not need to be evaluated either. You can fix this by adding a guard on ok2:
# (ok2,_) = fclose file w
| not ok2 = abort "fclose failed\n"
= appendNumInEndOfVmFiles xs dirname w
However, the typical way to do this would be to rewrite the function to return a *f instead of a String, so that this unique value is not lost. As long as the result is used, then, the fwritei is evaluated. You can potentially make the *f argument strict (i.e. add a ! in front). This would make sure that it is evaluated before entering the function, so that all lingering file closes have been performed.
There are some more issues with your code:
Here, w is used twice, which is illegal because it is of a strict type. You should use (ok2,w) in the guard to continue with the same environment.
# (ok2,_) = fclose file w
= appendNumInEndOfVmFiles xs dirname w
The appendNumInEndOfVmFiles needs to have a type context | FileSystem f to resolve overloading of fopen and fclose.
Lastly:
... even if its FWriteText or FWriteData ...
Just so you know: the difference would be that the first would write the integer in an ASCII representation whereas the second would write it binary as 4 or 8 bytes (depending on the bitwidth of your system).

lua oop deep copy a table

My deep copy code:
function deepcopy(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[deepcopy(orig_key)] = deepcopy(orig_value)
end
setmetatable(copy, deepcopy(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end
return copy
end
I'm trying to implement this to oop using self, but couldn't get it to work, here is what I've tried so far
function block:deepcopy()
local orig_type = type(self)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, self, nil do
copy[self:deepcopy(orig_key)] = deepcopy(orig_value)
end
setmetatable(copy, self:deeepcopy(getmetatable(self)))
else
copy = orig
end
return copy
In the OOP version of the function, self:deepcopy(something) with the method syntax (colon) doesn't do what you want it to. It is equivalent to self.deepcopy(self, something); the second argument something is ignored and you just end up trying to re-copy the same self over and over until there's a stack overflow. You have to do self.deepcopy(something) with a dot to pass something as the self argument (the argument that is copied).
Calling self.deepcopy inside the definition of the deepcopy method assumes that every subtable has a self.deepcopy function. If not, you will get an "attempt to call a nil value" error. But you could do this if you want every subtable to have its own version of deepcopy that is used when copying the immediate children of that table (keys, values, metatable). For instance, you could have a subtable whose deepcopy method does not copy the metatable. Here is the basic version where the subtable has the same deepcopy method:
local block = {}
function block:deepcopy()
if type(self) == 'table' then
local copy = {}
for key, value in pairs(self) do
copy[self.deepcopy(key)] = self.deepcopy(value)
end
return setmetatable(copy, self.deepcopy(getmetatable(self)))
else
return self
end
end
block.a = { a = 10, deepcopy = block.deepcopy }
block:deepcopy() -- works
block.a = { a = 10 }
block:deepcopy() -- error: "attempt to call a nil value (field 'deepcopy')"
But you don't need to rewrite the function at all to use it in object-oriented style. Try using your first definition of deepcopy. Do object.deepcopy = deepcopy, and then call object:deepcopy() and you will get a copy of the object.

How does Julia using behave on missing package?

So what exactly does Julia do with the statement using Foo if you don't have package Foo installed? As I understood Julia starts searching JULIA_LOAD_PATH, but how?
At the root level of JULIA_LOAD_PATH there must be a directory named Foo.jl where the Foo part may be case insensitive and the .jl suffix is optional?
And within this Foo.jl directory there must be a source file name Foo.jl with a module Foo?
using implicitly calls require which indirectly calls find_in_path:
function find_in_path(name::AbstractString, wd = pwd())
isabspath(name) && return name
base = name
# this is why `.jl` suffix is optional
if endswith(name,".jl")
base = name[1:end-3]
else
name = string(base,".jl")
end
if wd !== nothing
isfile(joinpath(wd,name)) && return joinpath(wd,name)
end
for prefix in [Pkg.dir(); LOAD_PATH]
path = joinpath(prefix, name)
isfile(path) && return abspath(path)
path = joinpath(prefix, base, "src", name)
isfile(path) && return abspath(path)
path = joinpath(prefix, name, "src", name)
isfile(path) && return abspath(path)
end
return nothing
end
The source code above shows that there is no additional manipulation on name, which means the Foo part should be case sensitive(Currently depend on the filesystem, see the comment below). And the directory name is unnecessary to be compatible with your file name, it can be anything as long as the directory is in your LOAD_PATH.

Creating Sequence of Sequences is Causing a StackOverflowException

I'm trying to take a large file and split it into many smaller files. The location where each split occurs is based on a predicate returned from examining the contents of each given line (isNextObject function).
I have attempted to read in the large file via the File.ReadLines function so that I can iterate through the file one line at a time without having to hold the entire file in memory. My approach was to group the sequence into a sequence of smaller sub-sequences (one per file to be written out).
I found a useful function that Tomas Petricek created on fssnip called groupWhen. This function worked great for my initial testing on a small subset of the file, but a StackoverflowException is thrown when using the real file. I am not sure how to adjust the groupWhen function to prevent this (I'm still an F# greenie).
Here is a simplified version of the code showing only the relevant parts that will recreate the StackoverflowExcpetion::
// This is the function created by Tomas Petricek where the StackoverflowExcpetion is occuring
module Seq =
/// Iterates over elements of the input sequence and groups adjacent elements.
/// A new group is started when the specified predicate holds about the element
/// of the sequence (and at the beginning of the iteration).
///
/// For example:
/// Seq.groupWhen isOdd [3;3;2;4;1;2] = seq [[3]; [3; 2; 4]; [1; 2]]
let groupWhen f (input:seq<_>) = seq {
use en = input.GetEnumerator()
let running = ref true
// Generate a group starting with the current element. Stops generating
// when it founds element such that 'f en.Current' is 'true'
let rec group() =
[ yield en.Current
if en.MoveNext() then
if not (f en.Current) then yield! group() // *** Exception occurs here ***
else running := false ]
if en.MoveNext() then
// While there are still elements, start a new group
while running.Value do
yield group() |> Seq.ofList }
This is the gist of the code making use Tomas' function:
module Extractor =
open System
open System.IO
open Microsoft.FSharp.Reflection
// ... elided a few functions include "isNextObject" which is
// a string -> bool (examines the line and returns true
// if the string meets the criteria to that we are at the
// start of the next inner file)
let writeFile outputDir file =
// ... write out "file" to the file system
// NOTE: file is a seq<string>
let writeFiles outputDir (files : seq<seq<_>>) =
files
|> Seq.iter (fun file -> writeFile outputDir file)
And here is the relevant code in the console application that makes use of the functions:
let lines = inputFile |> File.ReadLines
writeFiles outputDir (lines |> Seq.groupWhen isNextObject)
Any ideas on the proper way to stop groupWhen from blowing the stack? I'm not sure how I would convert the function to use an accumulator (or to use a continuation instead, which I think is the correct terminology).
The problem with this is that the group() function returns a list, which is an eagerly evaluated data structure, which means that every time you call group() it has to run to the end, collect all results in a list, and return the list. This means that the recursive call happens within that same evaluation - i.e. truly recursively, - thus creating stack pressure.
To mitigate this problem, you could just replace the list with a lazy sequence:
let rec group() = seq {
yield en.Current
if en.MoveNext() then
if not (f en.Current) then yield! group()
else running := false }
However, I would consider less drastic approaches. This example is a good illustration of why you should avoid doing recursion yourself and resort to ready-made folds instead.
For example, judging by your description, it seems that Seq.windowed may work for you.
It's easy to overuse sequences in F#, IMO. You can accidentally get stack overflows, plus they are slow.
So (not actually answering your question),
personally I would just fold over the seq of lines using something like this:
let isNextObject line =
line = "---"
type State = {
fileIndex : int
filename: string
writer: System.IO.TextWriter
}
let makeFilename index =
sprintf "File%i" index
let closeFile (state:State) =
//state.writer.Close() // would use this in real code
state.writer.WriteLine("=== Closing {0} ===",state.filename)
let createFile index =
let newFilename = makeFilename index
let newWriter = System.Console.Out // dummy
newWriter.WriteLine("=== Creating {0} ===",newFilename)
// create new state with new writer
{fileIndex=index + 1; writer = newWriter; filename=newFilename }
let writeLine (state:State) line =
if isNextObject line then
/// finish old file here
closeFile state
/// create new file here and return updated state
createFile state.fileIndex
else
//write the line to the current file
state.writer.WriteLine(line)
// return the unchanged state
state
let processLines (lines: string seq) =
//setup
let initialState = createFile 1
// process the file
let finalState = lines |> Seq.fold writeLine initialState
// tidy up
closeFile finalState
(Obviously a real version would use files rather than the console)
Yes, it is crude, but it is easy to reason about, with
no unpleasant surprises.
Here's a test:
processLines [
"a"; "b"
"---";"c"; "d"
"---";"e"; "f"
]
And here's what the output looks like:
=== Creating File1 ===
a
b
=== Closing File1 ===
=== Creating File2 ===
c
d
=== Closing File2 ===
=== Creating File3 ===
e
f
=== Closing File3 ===

Why is this recursion function parameter nil?

walk is a recursive function that walks the given tree and if walked over a file do something with it.
The "do something with it" should be changed.
I could use coroutine.yield(f) in walk but I wanted to know my mistake first.
As you see the argument lootfunc is given by a reference and should be called within walk.
But it gives me the error seen below. So why is the parameter lootfunc nil?
local KEYWORDS = {
"%.db[x]?",
"%.ojsn",
}
local function loot(d)
if MATCH == "path" then -- only look to the path not to the content
for i,keyword in pairs(KEYWORDS) do
if string.find(d,keyword) then
--coroutine.yield(d)
print(d)
end
end
end
end
local function walk (path,lootfunc)
for file in lfs.dir(path) do
if file ~= "." and file ~= ".." then
local f = path..'/'..file
local attr = lfs.attributes (f)
if(type(attr) == "table") then
if attr.mode == "directory" then
walk (f) -- next round
elseif attr.mode == "file" then
lootfunc(f)
end
end
end
end
end
walk("/path/",loot)
shadowed.lua:73: attempt to call local 'lootfunc' (a nil value)
stack traceback:
(command line):1: in function 'lootfunc'
shadowed.lua:73: in function 'walk'
shadowed.lua:71: in function 'walk'
shadowed.lua:71: in function 'walk'
shadowed.lua:88: in main chunk
[C]: in function 'dofile'
(command line):1: in function <(command line):1>
[C]: in function 'xpcall'
(command line):1: in main chunk
[C]: ?
You are calling walk(f) in the function walk, there's only one argument, the second argument is filled with nil, so change:
if attr.mode == "directory" then
walk(f) -- next round
to
if attr.mode == "directory" then
walk(f, lootfunc) -- next round

Resources