aboutsummaryrefslogtreecommitdiff
# SPDX-License-Identifier: CC0-1.0
#
# Process javascript files and resolve dependencies between them
#
# This file is part of Haketilo
#
# Copyright (C) 2021, Wojtek Kosior
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the CC0 1.0 Universal License as published by
# the Creative Commons Corporation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# CC0 1.0 Universal License for more details.

function read_file(filename,
		   imports_state, exports_state, line, record, result) {
    imports_state = "not_started"
    exports_state = "not_started"

    do {
	result = (getline line < filename)
	if (result < 0) {
	    printf "error reading %s", filename
	    exit 1
	}

	if (imports_state == "started" &&
	    line ~ /^([[:space:]]*\*[[:space:]]+)?IMPORT[[:space:]]+[_a-zA-Z][_a-zA-Z0-9]*[[:space:]]*$/) {
	    record = line

	    sub(/^([[:space:]]*\*[[:space:]]+)?IMPORT[[:space:]]+/, "", record)
	    sub(/([[:space:]]+$)/,                                  "", record)

	    imports[filename,++import_counts[filename]] = record
	}
	if (imports_state == "started" &&
	    line ~ /^([[:space:]]*\*[[:space:]]+)?IMPORTS_END[[:space:]]*$/)
	    imports_state = "finished"
	if (imports_state == "not_started" &&
	    line ~ /^([[:space:]]*\*[[:space:]]+)?IMPORTS_START[[:space:]]*$/)
	    imports_state = "started"

	if (exports_state == "started" &&
	    line ~ /^([[:space:]]*\*[[:space:]]+)?EXPORT[[:space:]]+[_a-zA-Z][_a-zA-Z0-9]*[[:space:]]*$/) {
	    record = line

	    sub(/^([[:space:]]*\*[[:space:]]+)?EXPORT[[:space:]]+/, "", record)
	    sub(/([[:space:]]+$)/,                                  "", record)

	    if (record in exports) {
		printf "ERROR: '%s' exported by both %s and %s\n",
		    exports[record], filename > "/dev/stderr"
	    }

	    provides[record] = filename
	    exports[filename,++export_counts[filename]] = record
	}
	if (exports_state == "started" &&
	    line ~ /^([[:space:]]*\*[[:space:]]+)?EXPORTS_END[[:space:]]*$/)
	    exports_state = "finished"
	if (exports_state == "not_started" &&
	    line ~ /^([[:space:]]*\*[[:space:]]+)?EXPORTS_START[[:space:]]*$/)
	    exports_state = "started"
    } while (result > 0)

    if (imports_state == "started") {
	printf "ERROR: Unclosed IMPORTS list in '%s'\n", filename	\
	    > "/dev/stderr"
	exit 1
    }

    if (exports_state == "started") {
	printf "ERROR: Unclosed EXPORTS list in '%s'\n", filename	\
	    > "/dev/stderr"
	exit 1
    }

    close(filename)
}

function print_file(filename,    line) {
    while ((getline line < filename) > 0)
	print(line)

    close(filename)
}

function print_imports_code(filename,    i, count, import_name) {
    count = import_counts[filename]
    for (i = 1; i <= count; i++) {
	import_name = imports[filename,i]
	printf "const %s = window.haketilo_exports.%s;\n",
	    import_name, import_name
    }
}

function print_exports_code(filename,    i, count, export_name) {
    count = export_counts[filename]
    for (i = 1; i <= count; i++) {
	export_name = exports[filename,i]
	printf "window.haketilo_exports.%s = %s;\n", export_name, export_name
    }
}

function partially_wrap_file(filename) {
    print_imports_code(filename)
    printf "\n\n"

    print_file(filename)

    printf "\n\n"
    print_exports_code(filename)
}

function wrap_file(filename) {
    print "\"use strict\";\n\n({fun: (function() {\n"

    partially_wrap_file(filename)

    print "\n})}).fun();"
}

function compute_dependencies(filename,    i, count, import_name, next_file) {
    if (processed[filename] == "used")
	return 0

    if (processed[filename] == "on_stack") {
	printf "import loop on %s\n", filename > "/dev/stderr"
	return 1
    }

    processed[filename] = "on_stack"

    count = import_counts[filename]
    for (i = 1; i <= count; i++) {
	import_name = imports[filename,i]
	if (!(import_name in provides)) {
	    printf "nothing exports %s, required by %s\n",
		import_name, filename > "/dev/stderr"
	    return 1
	}

	if (compute_dependencies(provides[import_name]) > 0) {
	    printf "when satisfying %s for %s\n",
		import_name, filename > "/dev/stderr"
	    return 1
	}
    }

    processed[filename] = "used"
    print filename

    return 0
}

function print_usage() {
    printf "usage:  %2 compute_scripts.awk script_dependencies|wrapped_code|partially_wrapped_code FILENAME[...]\n",
	ARGV[0] > "/dev/stderr"
    exit 1
}

function mock_exports_init() {
    provides["browser"]    = "exports_init.js"
    provides["is_chrome"]  = "exports_init.js"
    provides["is_mozilla"] = "exports_init.js"

    processed["exports_init.js"] = "used"
}

BEGIN {
    operation = ARGV[1]

    if (ARGC < 3)
	print_usage()

    root_filename = ARGV[2]

    for (i = 2; i < ARGC; i++)
	filenames[ARGV[i]]

    mock_exports_init()

    for (filename in filenames) {
	# A filename is allowed to appear multiple times in the list.
	# Let's only process it once.
	if (!(filename in processed))
	    read_file(filename)
	processed[filename] = "not_used"
    }

    if (operation == "script_dependencies") {
	print("exports_init.js")
	if (compute_dependencies(root_filename) > 0)
	    exit 1
    } else if (operation == "partially_wrapped_code") {
	partially_wrap_file(root_filename)
    } else if (operation == "wrapped_code") {
	wrap_file(root_filename)
    } else {
	print_usage()
    }
}