# jsp.awk --
# a script to simplify hand-writing code for the JavaSketchpad applet
# within HTML pages
#
# Version 2.8
# Copyright (C) 1997, 1998, 1999, 2009 Sebastian Lisken
# mail: Uni Bielefeld, FSP Mathematisierung,
# Postfach 100131, 33501 Bielefeld, Federal Republic of Germany
# email: lisken@Mathematik.Uni-Bielefeld.DE
# WWW: http://www.Mathematik.Uni-Bielefeld.DE/~lisken/jsp/
#
# Please keep the attribution in this notice and in jsp.awk's output intact
# if you use or redistribute the program in original or modified form.
#
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# 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
# GNU General Public License for more details.
#
# You should be able to find a copy of the GNU General Public License
# on the World Wide Web; if not, write to the
# Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
# WWW address for the GNU General Public License at the time of writing:
# http://www.gnu.org/copyleft/gpl.html
#
# - End of copyright notice -
# this should be a built-in, single AWK command
#
function clear (array) \
{
# this is canonical ...
# for (key in array) delete array [key]
# this is tricky, but portable
split ("", array)
}
# add1
#
# adds a value to a numbered array.
# The array's entries 1..n are filled.
# The value n is stored in array [0].
#
function add1 (array, value) \
{
array [++ array [0]] = value
}
# normalize
#
# Implements "normalization" as used in add2, getkey and existskey,
# determining what is to be ignored in the arrays implemented with
# those functions. (Case, spaces, punctuation, ...)
#
function normalize (string)
{
string = tolower (string)
gsub ("[^a-z0-9]", "", string)
return string
}
# add2
#
# adds a value to a string-indexed array with "normalized" lookup.
# A second array, keytable, holds the size and order of the array
# as well as the "normalized" keys.
#
function add2 (array, key, value, keytable) \
{
okey = getkey(key, keytable)
if (! (okey in array)) {
add1(keytable, okey)
}
array [okey] = value
}
# getkey
#
# adds a key to a table of "normalized" keys,
# or gets the original version if it already exists.
#
function getkey (key, keytable) \
{
if (! existskey(key, keytable)) {
keytable [nkey] = key
}
return keytable [nkey]
}
# existskey
#
# looks for a key in a table of "normalized" keys.
# Sets a variable "nkey" that provides access to the "normalized" version.
#
function existskey (key, keytable) \
{
nkey = "_" normalize(key)
return (nkey in keytable)
}
# the following two functions implement a line buffer
#
# get_line replaces AWK's getline
#
function get_line() \
{
if (buffer == "") {
return (getline) > 0
} else if ((n = index (buffer, "\n")) > 0) {
$0 = substr (buffer, 1, n-1)
buffer = substr (buffer, n+1)
} else {
$0 = buffer
buffer = ""
}
return 1
}
#
#
# buffer_line places a line before the rest of the input
#
function buffer_line (line) \
{
buffer = line "\n" buffer
}
#
#
# buffer_file reads a file and places it into the input stream
#
function buffer_file (filename)
{
filestatus = 1
fileline = ""
filebuffer = ""
file_content = ""
file_sep = ""
while (filestatus > 0)
{
if (filebuffer != "") {
fileline = filebuffer
filebuffer = ""
} else {
n = (getline fileline < filename)
if (n < 0) add_warning_noobj("error reading file '" filename "'")
if (n <= 0) filestatus = 0
}
if (filestatus == 1) {
if ((n = index (fileline, "")) > 0) {
filestatus = 2
fileline = substr (fileline, n+5)
sub ("[ \t]+$", "", fileline)
if (fileline == "") continue
}
}
if (filestatus > 1) {
if ((n = index (fileline, "")) > 0) {
filestatus = 1
filebuffer = substr (fileline, n+6)
fileline = substr (fileline, 1, n-1)
sub ("[ \t]+$", "", fileline)
if (fileline == "") continue
}
file_content = file_content file_sep fileline
file_sep = "\n"
}
}
close (filename)
buffer_jsp_line(file_content)
file_content = ""
}
# no_further_jsp_line
#
# tries to fetch the next line from the JSP section of the input.
# Return value: 0 on success, 1 on failure.
#
function no_further_jsp_line () \
{
if (status < 0) {
return 1
} else if (! get_line()) {
return 1
} else {
# for debugging purposes, needs UNIX-style cat and stderr
# print "*" NR "* " $0 | "cat >&2"
check_jsp_line()
# if this is the last line and it's empty, we'll say no
return (status < 0 && $0 == "")
}
}
# check_jsp_line
#
# checks if this is the last line of the applet
#
function check_jsp_line () \
{
# is there a tag?
if ((n = index (toupper ($0), "")) > 0) {
# remember text following it
after_applet = substr ($0, n + 6)
# restrict analysis to text preceding it
$0 = substr ($0, 1, n-1)
# set status
status = -status
}
}
# complete_jsp_line
#
# extends JSP lines that end in a backslash.
#
function complete_jsp_line () \
{
while (match ($0, "[ \t]*[\\\\]$"))
{
$0 = line1 = substr ($0, 1, RSTART - 1)
if (no_further_jsp_line()) {
break
}
if (NF > 0) {
$0 = line1 " " substr ($0, index ($0, substr ($1, 1, 1)))
} else {
$0 = line1
}
}
}
# complete_fields
#
# extends JSP lines to a minimum number of fields
# (expects that complete_jsp_line has already been called)
#
function complete_fields (min_fields) \
{
while (NF < min_fields)
{
line2 = $0
if (no_further_jsp_line()) {
break
}
complete_jsp_line()
if (NF > 0) {
$0 = line2 "\t" substr ($0, index ($0, substr ($1, 1, 1)))
} else {
$0 = line2
}
}
}
# buffer_jsp_line
#
# calls buffer_line and adjusts status
function buffer_jsp_line (line)
{
if (status < 0) {
status = -status
line = line "" after_applet
}
buffer_line(line)
}
# make_jsp_line
#
# As JSP under Java 6 can't handle tabs, we need to expand to 8 spaces.
function make_jsp_line (number, content)
{
jsp_line = "{" number "}"
jsp_line = jsp_line substr (" ", length (jsp_line) + 1) content
sub ("[ \t]+$", "", jsp_line)
return jsp_line
}
# get_directive
#
# parses a directive to the converter.
#
# If the directive is an assignment of something to a name, the function
# stores the assignment in aparam or jparam, depending on the name.
#
# If the directive is an "include" statement, it tries to
# read the file named in the statement and buffers it.
#
function get_directive (string) \
{
if (match (string, "^[A-Za-z0-9_]+[ \t]*[=][ \t]*.+$")) {
match (string, "[ \t]*[=][ \t]*")
name = substr (string, 1, RSTART - 1)
value = substr (string, RSTART + RLENGTH)
sub ("[ \t]+$", "", value)
if (tolower (name) == "construction") return
if (tolower (name) == "background") {
if (split (value, part, "[ ,]+") == 3) {
add2(jparam, "BackRed", part [1], jparams)
add2(jparam, "BackGreen", part [2], jparams)
add2(jparam, "BackBlue", part [3], jparams)
return
}
}
if (existskey(name, jparams)) {
add2(jparam, name, value, jparams)
} else {
add2(aparam, name, value, aparams)
}
} else if (match (string, "^[ \t]*include[ \t+]\"[^\"]+\"[ \t]*$")) {
match (string, "\".*\"")
buffer_file(substr (string, RSTART + 1, RLENGTH - 2))
}
}
# this records a warning
#
function add_warning (message) \
{
add1(warning, NR "\n" this_object "\n" message)
}
# this records a warning for a line that does not produce an object
#
function add_warning_noobj (message) \
{
add1(warning, NR "\n-\n" message)
}
# this records the number of the last object created
#
function record_object (jsp_reference, pseudo_object) \
{
jsp_object [this_object] = jsp_reference
if (pseudo_object) pseudo_objects [this_object] = ""
}
# this checks and returns an earlier recorded object
#
function get_object (jsp_table, reference) \
{
entry = jsp_table [reference]
if (substr (entry, 1, 1) == "?") {
add_warning("using failed reference")
}
return entry
}
# convert_refs
#
# handles all that $ business
# the macro parameter tells the function to substitute just macro parameters
# the macro_call parameter signals that we are converting parameters
# to a macro call, this changes the interpretation of relative numbers
function convert_refs (refstring, macro, macro_call) \
{
parts = split (refstring, part, "[$]")
# start with the first part, preceding any $
result = part [1]
for (j = 2; j <= parts; ++j)
{
this_part = part [j]
# this part of the string follows a $ sign
if (match (this_part, "^[!]?" namepat)) {
# it's a name - which one?
nameref = substr (this_part, 1, RLENGTH)
# using ! in name substitution is irrelevant
if (substr (nameref, 1, 1) == "!") {
nameref = substr (nameref, 2)
extra = "!"
} else {
extra = ""
}
# get the associated string and put that into the part
if (macro) {
if (nameref in macro_param) {
replacement = macro_param [nameref]
} else {
replacement = "$" extra nameref
}
} else {
if (nameref in named_object) {
replacement = get_object(named_object, nameref)
} else {
add_warning("Name '" nameref "' unknown")
replacement = "?" nameref "?"
}
}
this_part = replacement substr (this_part, RLENGTH + 1)
} else if (macro) {
# For macros, we don't want any other $s
this_part = "$" this_part
} else if (match (this_part, "^" numpat)) {
# it's a relative number
offset = substr (this_part, 1, RLENGTH) + 0
# convert to absolute and put it back into the part
other_object = this_object - offset + macro_call
if (other_object in jsp_object) {
replacement = get_object(jsp_object, other_object)
} else {
add_warning("Relative number out of range: " offset)
replacement = "?" offset "?"
}
this_part = replacement substr (this_part, RLENGTH + 1)
} else if (this_part == "") {
# it's an empty part - we've found two $'s. Keep one.
this_part = "$" (j < parts ? part [++j] : "")
} else {
# We don't know what it is - better hand the $ back
this_part = "$" this_part
}
# add the adjusted part
result = result this_part
}
return result
}
# parse_object
#
# parses a string of the form 'object-name (parameter, ...)'
# As parameters, a list containing object references, numeric constants
# and strings is expected, separated by commas and white space.
# (Object references in the form they have before $ processing.)
#
# Parameters:
# objstring is a string representing the parameter list.
# just_names, when true, tells the function to expect just names in the list.
# allow_special tells it to accept anything in double quotes as a parameter,
# removing the quotes.
#
# Returns 1 on success, 0 otherwise.
# Parameters are left in array 'param'.
# param [0] holds n, the number of parameters.
# param [1] .. param [n] are the parameters found.
# param ["rest"] is the rest of the parsed string, minus white space.
# param ["name"] is whatever came before the parameter list.
#
function parse_object (objstring, just_names, allow_special) \
{
match (objstring, "[ \t]*([([{;]|$)")
param ["name"] = substr (objstring, 1, RSTART-1)
rest = substr (objstring, RSTART)
sub ("^[ \t]*", "", rest)
param [0] = 0
if (substr (rest, 1, 1) != "(") {
param ["rest"] = rest
return 0
}
success = 1
rest = substr (rest, 2)
quote = allow_special ? "'\"" : "'"
while (1)
{
sub ("^[ \t]*", "", rest)
if (just_names) {
if (match (rest, "^[$]?" namepat "[" parend "]")) {
pname = substr (rest, 1, RLENGTH - 1)
if (substr (pname, 1, 1) == "$") {
pname = substr (pname, 2)
}
add1(param, pname)
rest = substr (rest, RSTART + RLENGTH - 1)
} else {
success = 0
break
}
} else {
if (match (rest, "^" parpat "[" parend "]")) {
add1(param, substr (rest, 1, RLENGTH - 1))
rest = substr (rest, RSTART + RLENGTH - 1)
} else if (index (quote, (q = substr (rest, 1, 1))) > 0) {
string = ""
while ((n = index (substr (rest, 2), q)) > 0)
{
n += 1
string = string substr (rest, 1, n)
rest = substr (rest, n+1)
if (substr (rest, 1, 1) != q) {
break
}
}
if (n > 0 && index (parend, substr (rest, 1, 1)) > 0) {
add1(param, string)
} else {
success = 0
rest = string rest
break
}
} else {
success = 0
break
}
}
sub ("^[ \t]*", "", rest)
c = substr (rest, 1, 1)
if (c == ")") {
rest = substr (rest, 2)
break
} else if (c != ",") {
success = 0
break
}
rest = substr (rest, 2)
}
sub ("^[ \t]*", "", rest)
param ["rest"] = rest
return success
}
# this prepares for a new applet
#
function init_applet () \
{
# aparam/aparams is a string-indexed array of the