jak2: significantly reduce the verbosity of the game.gp file (#2103)

This commit is contained in:
Tyler Wilding
2023-01-28 20:15:58 -05:00
committed by GitHub
parent 18507bc78e
commit 3d097e69e1
12 changed files with 548 additions and 4574 deletions
+62
View File
@@ -9,8 +9,10 @@
#include "ParseHelpers.h"
#include "common/goos/Printer.h"
#include "common/log/log.h"
#include "common/util/FileUtil.h"
#include "common/util/string_util.h"
#include "common/util/unicode_util.h"
#include "third-party/fmt/core.h"
@@ -53,6 +55,7 @@ Interpreter::Interpreter(const std::string& username) {
{"begin", &Interpreter::eval_begin},
{"exit", &Interpreter::eval_exit},
{"read", &Interpreter::eval_read},
{"read-data-file", &Interpreter::eval_read_data_file},
{"read-file", &Interpreter::eval_read_file},
{"print", &Interpreter::eval_print},
{"inspect", &Interpreter::eval_inspect},
@@ -82,6 +85,9 @@ Interpreter::Interpreter(const std::string& username) {
{"string-ref", &Interpreter::eval_string_ref},
{"string-length", &Interpreter::eval_string_length},
{"string-append", &Interpreter::eval_string_append},
{"string-starts-with?", &Interpreter::eval_string_starts_with},
{"string-ends-with?", &Interpreter::eval_string_ends_with},
{"string-split", &Interpreter::eval_string_split},
{"ash", &Interpreter::eval_ash},
{"symbol->string", &Interpreter::eval_symbol_to_string},
{"string->symbol", &Interpreter::eval_string_to_symbol},
@@ -1002,6 +1008,23 @@ Object Interpreter::eval_read(const Object& form,
return Object::make_empty_list();
}
/*!
* Reads list data from a file, returns the pair. Not a lot of safety here!
*/
Object Interpreter::eval_read_data_file(const Object& form,
Arguments& args,
const std::shared_ptr<EnvironmentObject>& env) {
(void)env;
vararg_check(form, args, {ObjectType::STRING}, {});
try {
return reader.read_from_file({args.unnamed.at(0).as_string()->data}).as_pair()->cdr;
} catch (std::runtime_error& e) {
throw_eval_error(form, std::string("reader error inside of read-file:\n") + e.what());
}
return Object::make_empty_list();
}
/*!
* Open and run the Reader on a text file.
*/
@@ -1648,6 +1671,45 @@ Object Interpreter::eval_string_append(const Object& form,
return StringObject::make_new(result);
}
Object Interpreter::eval_string_starts_with(const Object& form,
Arguments& args,
const std::shared_ptr<EnvironmentObject>& env) {
(void)env;
vararg_check(form, args, {ObjectType::STRING, ObjectType::STRING}, {});
auto& str = args.unnamed.at(0).as_string()->data;
auto& suffix = args.unnamed.at(1).as_string()->data;
if (str_util::starts_with(str, suffix)) {
return SymbolObject::make_new(reader.symbolTable, "#t");
}
return SymbolObject::make_new(reader.symbolTable, "#f");
}
Object Interpreter::eval_string_ends_with(const Object& form,
Arguments& args,
const std::shared_ptr<EnvironmentObject>& env) {
(void)env;
vararg_check(form, args, {ObjectType::STRING, ObjectType::STRING}, {});
auto& str = args.unnamed.at(0).as_string()->data;
auto& suffix = args.unnamed.at(1).as_string()->data;
if (str_util::ends_with(str, suffix)) {
return SymbolObject::make_new(reader.symbolTable, "#t");
}
return SymbolObject::make_new(reader.symbolTable, "#f");
}
Object Interpreter::eval_string_split(const Object& form,
Arguments& args,
const std::shared_ptr<EnvironmentObject>& env) {
(void)env;
vararg_check(form, args, {ObjectType::STRING, ObjectType::STRING}, {});
auto& str = args.unnamed.at(0).as_string()->data;
auto& delim = args.unnamed.at(1).as_string()->data;
return pretty_print::build_list(str_util::split(str, delim.at(0)));
}
Object Interpreter::eval_ash(const Object& form,
Arguments& args,
const std::shared_ptr<EnvironmentObject>& env) {
+12
View File
@@ -116,6 +116,9 @@ class Interpreter {
Object eval_read(const Object& form,
Arguments& args,
const std::shared_ptr<EnvironmentObject>& env);
Object eval_read_data_file(const Object& form,
Arguments& args,
const std::shared_ptr<EnvironmentObject>& env);
Object eval_read_file(const Object& form,
Arguments& args,
const std::shared_ptr<EnvironmentObject>& env);
@@ -197,6 +200,15 @@ class Interpreter {
Object eval_string_append(const Object& form,
Arguments& args,
const std::shared_ptr<EnvironmentObject>& env);
Object eval_string_starts_with(const Object& form,
Arguments& args,
const std::shared_ptr<EnvironmentObject>& env);
Object eval_string_ends_with(const Object& form,
Arguments& args,
const std::shared_ptr<EnvironmentObject>& env);
Object eval_string_split(const Object& form,
Arguments& args,
const std::shared_ptr<EnvironmentObject>& env);
Object eval_ash(const Object& form,
Arguments& args,
const std::shared_ptr<EnvironmentObject>& env);
+24
View File
@@ -5,6 +5,7 @@
#include "FileUtil.h"
#include <algorithm>
#include <cstdio> /* defines FILENAME_MAX */
#include <cstdlib>
#include <fstream>
@@ -338,6 +339,29 @@ std::string base_name_no_ext(const std::string& filename) {
;
}
std::string split_path_at(const fs::path& path, const std::vector<std::string>& folders) {
std::string split_str = "";
for (const auto& folder : folders) {
#ifdef _WIN32
split_str += folder + "\\";
#else
split_str += folder + "/";
#endif
}
const auto& path_str = path.u8string();
return path_str.substr(path_str.find(split_str) + split_str.length());
}
std::string convert_to_unix_path_separators(const std::string& path) {
#ifdef _WIN32
std::string copy = path;
std::replace(copy.begin(), copy.end(), '\\', '/');
return copy;
#else
return path;
#endif
}
void ISONameFromAnimationName(char* dst, const char* src) {
// The Animation Name is a bunch of words separated by dashes
+2
View File
@@ -53,6 +53,8 @@ std::string combine_path(const std::string& parent, const std::string& child);
bool file_exists(const std::string& path);
std::string base_name(const std::string& filename);
std::string base_name_no_ext(const std::string& filename);
std::string split_path_at(const fs::path& path, const std::vector<std::string>& folders);
std::string convert_to_unix_path_separators(const std::string& path);
void MakeISOName(char* dst, const char* src);
void ISONameFromAnimationName(char* dst, const char* src);
void assert_file_exists(const char* path, const char* error_message);
+3 -6
View File
@@ -13,15 +13,12 @@ bool contains(const std::string& s, const std::string& substr) {
}
bool starts_with(const std::string& s, const std::string& prefix) {
return s.rfind(prefix) == 0;
return s.size() >= prefix.size() && 0 == s.compare(0, prefix.size(), prefix);
}
bool ends_with(const std::string& s, const std::string& suffix) {
if (s.length() >= suffix.length()) {
return !s.compare(s.length() - suffix.length(), suffix.length(), suffix);
} else {
return false;
}
return s.size() >= suffix.size() &&
0 == s.compare(s.size() - suffix.size(), suffix.size(), suffix);
}
std::string ltrim(const std::string& s) {
+1 -1
View File
@@ -232,7 +232,6 @@
("game-task.o" "game-task")
("game-save.o" "game-save")
("settings.o" "settings")
("pckernel.o" "pckernel") ;; added
("mood-tables.o" "mood-tables")
("mood-tables2.o" "mood-tables2")
("mood.o" "mood")
@@ -326,6 +325,7 @@
("prototype.o" "prototype")
("main-collide.o" "main-collide")
("video.o" "video")
("pckernel.o" "pckernel") ;; added
("main.o" "main")
("collide-cache.o" "collide-cache")
("collide-debug.o" "collide-debug")
+2 -1
View File
@@ -29,8 +29,8 @@
("dma-buffer.o" "dma-buffer")
("dma-bucket.o" "dma-bucket")
("dma-disasm.o" "dma-disasm")
("pckernel-h.o" "pckernel-h") ;; added
("pad.o" "pad")
("pckernel-h.o" "pckernel-h") ;; added
("gs.o" "gs")
("display-h.o" "display-h")
("geometry.o" "geometry")
@@ -326,6 +326,7 @@
("prototype.o" "prototype")
("main-collide.o" "main-collide")
("video.o" "video")
("pckernel.o" "pckernel") ;; added
("main.o" "main")
("collide-cache.o" "collide-cache")
("collide-debug.o" "collide-debug")
+175 -4451
View File
File diff suppressed because it is too large Load Diff
+199
View File
@@ -0,0 +1,199 @@
;;-*-Lisp-*-
;; TODO extract most of this into a common lib that isn't so game dependent
;;;;;;;;;;;;;;;;;;;;;;;
;; Build system macros
;;;;;;;;;;;;;;;;;;;;;;;
(defun gc-file->o-file (filename)
"Get the name of the object file for the given GOAL (*.gc) source file."
(string-append "$OUT/obj/" (stem filename) ".o")
)
(defmacro goal-src (src-file &rest deps)
"Add a GOAL source file with the given dependencies"
`(let ((output-file ,(gc-file->o-file src-file)))
(set! *all-gc* (cons output-file *all-gc*))
(defstep :in ,(string-append "goal_src/jak2/" src-file)
;; use goal compiler
:tool 'goalc
;; will output the obj file
:out (list output-file)
;; dependencies are the obj files
:dep '(,@(apply gc-file->o-file deps))
)
)
)
(defun make-src-sequence-elt (current previous prefix)
"Helper for goal-src-sequence"
`(let ((output-file ,(gc-file->o-file current)))
(set! *all-gc* (cons output-file *all-gc*))
(defstep :in ,(string-append "goal_src/jak2/" prefix current)
:tool 'goalc
:out (list output-file)
:dep '(,(gc-file->o-file previous))
)
)
)
;; TODO - deps should probably just treated as a proper list to refactor duplication
(defmacro goal-src-sequence (prefix &key (deps '()) &rest sequence)
"Add a sequence of GOAL files (each depending on the previous) in the given directory,
with all depending on the given deps."
(let* ((first-thing `(goal-src ,(string-append prefix (first sequence)) ,@deps))
(result (cons first-thing '()))
(iter result))
(let ((prev (first sequence))
(in-iter (rest sequence)))
(while (not (null? in-iter))
;; (fmt #t "{} dep on {}\n" (first in-iter) prev)
(let ((next (make-src-sequence-elt (first in-iter) prev prefix)))
(set-cdr! iter (cons next '()))
(set! iter (cdr iter))
)
(set! prev (car in-iter))
(set! in-iter (cdr in-iter))
)
)
`(begin ,@result)
)
)
(defun cgo (output-name desc-file-name)
"Add a CGO with the given output name (in $OUT/iso) and input name (in goal_src/jak2/dgos)"
(let ((out-name (string-append "$OUT/iso/" output-name)))
(defstep :in (string-append "goal_src/jak2/dgos/" desc-file-name)
:tool 'dgo
:out `(,out-name)
)
(set! *all-cgos* (cons out-name *all-cgos*))
)
)
(defun tpage-name (id)
"Get the name of the tpage obj file with the given id"
(fmt #f "tpage-{}.go" id)
)
(defmacro copy-texture (tpage-id)
"Copy a texture from the game, using the given tpage ID"
(let* ((path (string-append "$DECOMP/raw_obj/" (tpage-name tpage-id))))
`(defstep :in ,path
:tool 'copy
:out '(,(string-append "$OUT/obj/" (tpage-name tpage-id))))))
(defmacro copy-textures (&rest ids)
`(begin
,@(apply (lambda (x) `(copy-texture ,x)) ids)
)
)
(defmacro copy-go (name)
(let* ((path (string-append "$DECOMP/raw_obj/" name ".go")))
`(defstep :in ,path
:tool 'copy
:out '(,(string-append "$OUT/obj/" name ".go")))))
(defmacro copy-gos (&rest gos)
`(begin
,@(apply (lambda (x) `(copy-go ,x)) gos)
)
)
(defmacro group (name &rest stuff)
`(defstep :in ""
:tool 'group
:out '(,(string-append "GROUP:" name))
:dep '(,@stuff))
)
(defun group-list (name stuff)
(defstep :in ""
:tool 'group
:out `(,(string-append "GROUP:" name))
:dep stuff)
)
(defun copy-iso-file (name subdir ext)
(let* ((path (string-append "$ISO/" subdir name ext))
(out-name (string-append "$OUT/iso/" name ext)))
(defstep :in path
:tool 'copy
:out `(,out-name))
out-name))
(defmacro copy-strs (&rest strs)
`(begin ,@(apply (lambda (x) `(set! *all-str* (cons (copy-iso-file ,x "STR/" ".STR") *all-str*))) strs)))
(defmacro copy-sbk-files (&rest files)
`(begin ,@(apply (lambda (x) `(set! *all-sbk* (cons (copy-iso-file ,x "SBK/" ".SBK") *all-sbk*))) files)))
(defmacro copy-mus-files (&rest files)
`(begin ,@(apply (lambda (x) `(set! *all-mus* (cons (copy-iso-file ,x "MUS/" ".MUS") *all-mus*))) files)))
(defmacro copy-vag-files (&rest files)
`(begin ,@(apply (lambda (x) `(set! *all-vag* (cons (copy-iso-file "VAGWAD" "VAG/" (string-append "." ,x)) *all-vag*))) files)))
(defun reverse-list (list)
(let ((new-list '())
(curr-elt list))
(while (not (null? curr-elt))
(set! new-list (cons (car curr-elt) new-list))
(set! curr-elt (cdr curr-elt)))
new-list))
(defmacro cgo-file (dgo-file-name deps)
;; First read in the DGO file, it has pretty much everything we need
(let ((dgo-data (car (read-data-file (string-append "goal_src/jak2/dgos/" dgo-file-name)))))
;; Get the name of the DGO
(let ((dgo-name (car dgo-data))
(files (cdr dgo-data))
(gsrc-seq-args '())
(textures '())
(gos '()))
;; create the dgo step
(cgo dgo-name dgo-file-name)
;; Now we iterate through the list of files, skipping ones we've already processed
;; and creating steps for the ones that are new!
(while (not (null? files))
(let ((file-name (car (car files)))
(obj-name (car (cdr (car files)))))
;; Check to see if we've already handled this file
(when (not (car (hash-table-try-ref *file-entry-map* file-name)))
;; Depending on the type of file, generate the appropriate steps
(cond
((string-ends-with? file-name ".o")
;; build up a list of all gsrc files needing to be compiled
(let ((gsrc-path (get-gsrc-path obj-name)))
(set! gsrc-seq-args (cons gsrc-path gsrc-seq-args))))
((string-starts-with? obj-name "tpage-")
;; copy textures
(let ((tpage-id (car (cdr (string-split obj-name "-")))))
(set! textures (cons tpage-id textures))))
((string-ends-with? file-name ".go")
;; copy art files
(set! gos (cons (stem file-name) gos))))
;; Update the map so this file isn't processed again
(hash-table-set! *file-entry-map* file-name #f)))
(set! files (cdr files)))
;; TODO - need an `append`!, reverse lists by re-cons'ing them for now
(set! gsrc-seq-args (reverse-list gsrc-seq-args))
(set! textures (reverse-list textures))
(set! gos (reverse-list gos))
`(begin
;; macros can't return nothing, so these macros assume they will be given a non-empty list
(when (not (null? '(,@gsrc-seq-args)))
(goal-src-sequence "" :deps ,deps ,@gsrc-seq-args))
(when (not (null? '(,@textures)))
(copy-textures ,@textures))
(when (not (null? '(,@gos)))
(copy-gos ,@gos)))
)
))
+58
View File
@@ -4,6 +4,7 @@
#include "common/log/log.h"
#include "common/util/FileUtil.h"
#include "common/util/Timer.h"
#include "common/util/string_util.h"
#include "goalc/make/Tools.h"
@@ -53,6 +54,11 @@ MakeSystem::MakeSystem(const std::string& username) : m_goos(username) {
return handle_stem(obj, args, env);
});
m_goos.register_form("get-gsrc-path", [=](const goos::Object& obj, goos::Arguments& args,
const std::shared_ptr<goos::EnvironmentObject>& env) {
return handle_get_gsrc_path(obj, args, env);
});
m_goos.register_form("map-path!", [=](const goos::Object& obj, goos::Arguments& args,
const std::shared_ptr<goos::EnvironmentObject>& env) {
return handle_map_path(obj, args, env);
@@ -64,6 +70,12 @@ MakeSystem::MakeSystem(const std::string& username) : m_goos(username) {
return handle_set_output_prefix(obj, args, env);
});
m_goos.register_form("set-gsrc-folder!",
[=](const goos::Object& obj, goos::Arguments& args,
const std::shared_ptr<goos::EnvironmentObject>& env) {
return handle_set_gsrc_folder(obj, args, env);
});
m_goos.set_global_variable_to_symbol("ASSETS", "#t");
set_constant("*iso-data*", file_util::get_file_path({"iso_data"}));
@@ -193,6 +205,39 @@ goos::Object MakeSystem::handle_stem(const goos::Object& form,
return goos::StringObject::make_new(input.stem().u8string());
}
goos::Object MakeSystem::handle_get_gsrc_path(const goos::Object& form,
goos::Arguments& args,
const std::shared_ptr<goos::EnvironmentObject>& env) {
if (m_gsrc_folder.empty()) {
throw std::runtime_error("`set-gsrc-folder!` was not called before a `get-src-path`");
}
m_goos.eval_args(&args, env);
va_check(form, args, {goos::ObjectType::STRING}, {});
const auto& file_name = args.unnamed.at(0).as_string()->data;
// Keep things fast by scanning the gsrc directory _once_ on the first call
if (m_gsrc_files.empty()) {
auto folder = file_util::get_file_path(m_gsrc_folder);
auto src_files = file_util::find_files_recursively(folder, std::regex(".*\\.gc"));
for (const auto& path : src_files) {
auto name = file_util::base_name_no_ext(path.u8string());
auto gsrc_path =
file_util::convert_to_unix_path_separators(file_util::split_path_at(path, m_gsrc_folder));
// TODO - this is only "safe" because the current OpenGOAL system requires globally unique
// file names
m_gsrc_files.emplace(name, gsrc_path);
}
}
if (m_gsrc_files.count(file_name) != 0) {
return goos::StringObject::make_new(m_gsrc_files.at(file_name));
} else {
return goos::SymbolObject::make_new(m_goos.reader.symbolTable, "#f");
}
}
goos::Object MakeSystem::handle_map_path(const goos::Object& form,
goos::Arguments& args,
const std::shared_ptr<goos::EnvironmentObject>& env) {
@@ -217,10 +262,23 @@ goos::Object MakeSystem::handle_set_output_prefix(
return goos::Object::make_empty_list();
}
goos::Object MakeSystem::handle_set_gsrc_folder(
const goos::Object& form,
goos::Arguments& args,
const std::shared_ptr<goos::EnvironmentObject>& env) {
m_goos.eval_args(&args, env);
va_check(form, args, {goos::ObjectType::STRING}, {});
const auto& folder = args.unnamed.at(0).as_string()->data;
m_gsrc_folder = str_util::split(folder, '/');
return goos::Object::make_empty_list();
}
void MakeSystem::get_dependencies(const std::string& master_target,
const std::string& output,
std::vector<std::string>* result,
std::unordered_set<std::string>* result_set) const {
// fmt::print(output + "\n");
if (result_set->find(output) != result_set->end()) {
return;
}
+10
View File
@@ -30,6 +30,10 @@ class MakeSystem {
goos::Arguments&,
const std::shared_ptr<goos::EnvironmentObject>& env);
goos::Object handle_get_gsrc_path(const goos::Object& obj,
goos::Arguments&,
const std::shared_ptr<goos::EnvironmentObject>& env);
goos::Object handle_map_path(const goos::Object& obj,
goos::Arguments& args,
const std::shared_ptr<goos::EnvironmentObject>& env);
@@ -38,6 +42,10 @@ class MakeSystem {
goos::Arguments& args,
const std::shared_ptr<goos::EnvironmentObject>& env);
goos::Object handle_set_gsrc_folder(const goos::Object& obj,
goos::Arguments& args,
const std::shared_ptr<goos::EnvironmentObject>& env);
std::vector<std::string> get_dependencies(const std::string& target) const;
std::vector<std::string> filter_dependencies(const std::vector<std::string>& all_deps);
@@ -76,4 +84,6 @@ class MakeSystem {
std::unordered_map<std::string, std::shared_ptr<MakeStep>> m_output_to_step;
std::unordered_map<std::string, std::shared_ptr<Tool>> m_tools;
PathMap m_path_map;
std::vector<std::string> m_gsrc_folder;
std::map<std::string, std::string> m_gsrc_files = {};
};
@@ -1,115 +0,0 @@
# Generates a file with all `game.gp` code taking the `*.gd` files as input
# example - python .\scripts\gsrc\skeleton_creation\generate_dgo_project_code.py --game jak2
import argparse
import glob
from pathlib import Path
import json
import os
# TODO - this is duplication code, but i don't feel like dealing with python's module system right now, its out of `utils.py`
jak1_files = None
jak2_files = None
with open('./goal_src/jak1/build/all_objs.json', 'r') as f:
jak1_files = json.load(f)
with open('./goal_src/jak2/build/all_objs.json', 'r') as f:
jak2_files = json.load(f)
def get_file_list(game_name):
if game_name == "jak1":
return jak1_files
else:
return jak2_files
def get_project_path_from_filename(game_name, file_name):
file_list = get_file_list(game_name)
src_path = ""
for f in file_list:
if f[2] != 3:
continue
if f[0] == file_name:
src_path = f[4]
break
path = "./goal_src/{}/{}/{}.gc".format(game_name, src_path, file_name)
if not os.path.exists(path):
print("{} couldn't find in /goal_src/{}!".format(file_name, game_name))
exit(1)
return "{}/{}.gc".format(src_path, file_name)
parser = argparse.ArgumentParser("generate_dgo_project_code")
parser.add_argument("--game", help="The name of the game", type=str)
# TODO - it isn't really established what this should actually be, for example, at the time of writing
# jak 2's other DGOs depend on a `los-control` file which is a random file near the end (but not quite) in GAME.CGO
#
# Until a proper system is figured out, this is basically a best-guess so there's no point trying to be accurate at this stage
parser.add_argument("--dep", help="Arbitrary file to make all the DGOs depend upon", type=str)
args = parser.parse_args()
dgos_to_skip = ["common.gd", "engine.gd", "game.gd", "vi1.gd", "kernel.gd", "cta.gd", "lwidea.gd", "cwi.gd", "pri.gd"]
output = []
file_locations = None
for name in glob.glob('./goal_src/{}/dgos/*.gd'.format(args.game)):
skip_it = False
for to_skip in dgos_to_skip:
if to_skip in name:
skip_it = True
break
if skip_it:
continue
# read the file
dgo_name = Path(name).stem.upper()
# first print a comment header
output.append(";;;;;;;;;;;;;;;;;;;;;\n")
output.append(";; {}\n".format(dgo_name))
output.append(";;;;;;;;;;;;;;;;;;;;;\n")
output.append("\n")
# print cgo call
output.append("(cgo \"{}.DGO\" \"{}.gd\")\n".format(dgo_name, dgo_name.lower()))
output.append("\n")
object_paths = []
texture_ids = []
# anything that ends with .go
copy_names = []
with open(name, "r") as f:
dgo_lines = f.readlines()[1:] # skip the first line, dont care about it
for line in dgo_lines:
# gross parsing, as usual without a nice data format
cleaned_line = line.strip().replace("\"", "").replace("(", "").replace(")", "")
if cleaned_line == "":
continue
object_name = cleaned_line.split(" ")[0]
object_name_no_ext = object_name.split(".")[0]
if ".o" in object_name:
object_paths.append(get_project_path_from_filename(args.game, object_name_no_ext))
elif ".go" in object_name:
# tpages are different
if "tpage" in object_name:
texture_ids.append(object_name_no_ext.split("-")[1])
else:
copy_names.append(object_name_no_ext)
# Construct the rest of the file
if len(object_paths) > 0:
output.append("(goal-src-sequence\n \"\"\n :deps (\"$OUT/obj/{}.o\")\n".format(args.dep))
for path in object_paths:
output.append(" \"{}\"\n".format(path))
output.append(" )\n\n")
if len(texture_ids) > 0:
output.append("(copy-textures {})\n\n".format(" ".join(texture_ids)))
if len(copy_names) > 0:
output.append("(copy-gos\n")
for name in copy_names:
output.append(" \"{}\"\n".format(name))
output.append(" )\n\n")
with open('dgo-proj-code.gp', 'w') as out_file:
out_file.writelines(output)