~trn/reduce-algebra

5bdfc413d23b31d1aafe5d7bd262b30bd50cb8b5 — Jeffrey H. Johnson 17 days ago 193b380 + ff6a062
Merge branch 'svn/trunk'
M csl/cslbase/Makefile.am => csl/cslbase/Makefile.am +35 -17
@@ 805,9 805,9 @@ endif !fox
SHARED=$(cslbase)/fwin.h $(cslbase)/log.h $(cslbase)/termed.h \
	$(cslbase)/fwin.cpp $(cslbase)/termed.cpp 

$(FOXDEPS) &:	$(csl)/fox/CHANGED $(SHARED)
$(FOXDEPS):	$(csl)/fox/CHANGED $(SHARED)
	@printf "About to build FOX for %s ($@)\n" `pwd`
	$(TRACE)@cd ../fox && $(MAKE) && $(MAKE) install
	$(TRACE)@$(MAKE) -C ../fox install


if crlibm


@@ 846,9 846,9 @@ if conservative
alloctest_DEPENDENCIES += $(CRLIBMDEPS)
endif conservative

$(CRLIBMDEPS) &:
$(CRLIBMDEPS):
	@printf "About to build crlibm for %s ($@)\n" `pwd`
	$(TRACE)@cd ../crlibm && $(MAKE) install
	$(TRACE)@$(MAKE) -C ../crlibm install

else !crlibm
CRINC =


@@ 890,9 890,9 @@ if conservative
alloctest_DEPENDENCIES += $(LIBFFIDEPS)
endif conservative

$(LIBFFIDEPS) &:
$(LIBFFIDEPS):
	@printf "About to build libffi for %s ($@)\n" `pwd`
	$(TRACE)@cd ../libffi && $(MAKE) install
	$(TRACE)@$(MAKE) -C ../libffi install

SOFTFLOATDEPS=../lib/libsoftfloat.a ../include/softfloat.h



@@ 927,9 927,9 @@ if conservative
alloctest_DEPENDENCIES    += $(SOFTFLOATDEPS)
endif conservative

$(SOFTFLOATDEPS) &:
$(SOFTFLOATDEPS):
	@printf "About to build softfloat for %s ($@)\n" `pwd`
	$(TRACE)@cd ../softfloat && $(MAKE) install
	$(TRACE)@$(MAKE) -C ../softfloat install

# Note that all the compilation of these files must be performed using
# a Make "default" action so that the dependencies shown here are not


@@ 1195,9 1195,9 @@ $(csl)/wxWidgets/wxWidgets_revision:	\

WXDEPS=../bin/wx-config

../bin/wx-config &:	$(csl)/wxWidgets/wxWidgets_revision
../bin/wx-config:	$(csl)/wxWidgets/wxWidgets_revision
	@printf "About to build wxWidgets for %s ($@)\n" `pwd`
	$(TRACE)@cd ../wxWidgets && $(MAKE) install
	$(TRACE)@$(MAKE) -C ../wxWidgets install

wxfontdemo$(EXEEXT): $(wxfontdemo_OBJECTS) $(wxfontdemo_DEPENDENCIES) $(WXDEPS)
	$(CXXLINKTO) $(DEST)wxfontdemo$(EXEEXT) $(wxfontdemo_OBJECTS) $(wxfontdemo_LDADD) $(AM_LIBS) $(LIBS) $(CONAP)


@@ 1272,6 1272,7 @@ endif !wx
HDRS = $(FOXDEPS) $(CRDEP) $(FFIDEP) $(SOFTFLOATDEP)

csl$(EXEEXT):	$(csl_OBJECTS) $(csl_DEPENDENCIES) $(FOXDEPS) $(WXDEPS) $(HDRS) $(CSLCOM) $(CYG_CSL)
	@printf "Relinking $@ because of $?\n"
if windows
# In a way that may be seen as strange I want to end up with 3 executables
# for CSL. The most obvious is "csl.exe" which I link from all the object


@@ 1314,12 1315,14 @@ endif !wx
if windows
if gui
csl$(COM):	$(csl_OBJECTS) $(csl_DEPENDENCIES) $(FOXDEPS) $(WXDEPS) $(HDRS)
	@printf "Relinking $@ because of $?\n"
	$(CXXLINKTO) $(DEST)csl$(COM) $(csl_OBJECTS) $(csl_LDADD) $(AM_LIBS) $(LIBS) $(CONAP)
	$(STRIP) csl$(COM)
endif gui
endif windows

flatcsl$(EXEEXT):	$(flatcsl_OBJECTS) $(csl_DEPENDENCIES) $(FOXDEPS) $(WXDEPS) $(HDRS) $(FLATCSLCOM) $(CYG_FLATCSL)
	@printf "Relinking $@ because of $?\n"
if windows
if gui
	$(CP) $(cslbase)/csl csl-temp


@@ 1353,19 1356,23 @@ endif !wx
if windows
if gui
flatcsl$(COM):	$(flatcsl_OBJECTS) $(csl_DEPENDENCIES) $(FOXDEPS) $(WXDEPS) $(HDRS)
	@printf "Relinking $@ because of $?\n"
	$(CXXLINKTO) $(DEST)flatcsl$(COM) $(flatcsl_OBJECTS) $(flatcsl_LDADD) $(AM_LIBS) $(LIBS) $(CONAP)
	$(STRIP) flatcsl$(COM)
endif gui
endif windows

contest$(EXEEXT): $(contest_OBJECTS) $(contest_DEPENDENCIES)
	@printf "Relinking $@ because of $?\n"
	$(CXXLINKTO) $(DEST)contest$(EXEEXT) $(contest_OBJECTS) $(contest_LDADD) $(AM_LIBS) $(LIBS)

alloctest$(EXEEXT): $(alloctest_OBJECTS) $(alloctest_DEPENDENCIES)
	@printf "Relinking $@ because of $?\n"
	$(CXXLINKTO) $(DEST)alloctest$(EXEEXT) $(alloctest_OBJECTS) $(alloctest_LDADD) $(AM_LIBS) $(LIBS) $(WINAP)

reduce$(EXEEXT):	$(reduce_OBJECTS) $(reduce_DEPENDENCIES) $(FOXDEPS) \
			$(WXDEPS) $(generated_lisp) $(HDRS) $(REDUCECOM) $(CYG_REDUCE)
	@printf "Relinking $@ because of $?\n"
if windows
# See "gui-or-not.txt" for an explanation of what this is about...
if gui


@@ 1414,12 1421,17 @@ if windows
if gui
reduce$(COM):	$(reduce_OBJECTS) $(reduce_DEPENDENCIES) $(FOXDEPS) \
			$(WXDEPS) $(generated_lisp) $(HDRS)
	@printf "Relinking $@ because of $?\n"
	$(CXXLINKTO) $(DEST)reduce$(COM) $(reduce_OBJECTS) $(reduce_LDADD) $(AM_LIBS) $(LIBS) $(CONAP)
	$(STRIP) reduce$(COM)
endif gui
endif windows

bootstrapreduce$(EXEEXT):	$(bootstrapreduce_OBJECTS) $(bootstrapreduce_DEPENDENCIES) $(FOXDEPS) $(WXDEPS) $(HDRS) $(BOOTSTRAPREDUCECOM) $(CYG_BOOTSTRAPREDUCE)
bootstrapreduce$(EXEEXT):	$(bootstrapreduce_OBJECTS) \
		$(bootstrapreduce_DEPENDENCIES) $(FOXDEPS) \
		$(WXDEPS) $(HDRS) $(BOOTSTRAPREDUCECOM) \
		$(CYG_BOOTSTRAPREDUCE)
	@printf "Relinking $@ because of $?\n"
if windows
# See "gui-or-not.txt" for an explanation of what this is about...
	$(CP) $(cslbase)/bootstrapreduce bootstrapreduce-temp


@@ 1450,12 1462,14 @@ endif windows
if windows
if gui
bootstrapreduce$(COM):	$(bootstrapreduce_OBJECTS) $(bootstrapreduce_DEPENDENCIES) $(FOXDEPS) $(WXDEPS) $(HDRS)
	@printf "Relinking $@ because of $?\n"
	$(CXXLINKTO) $(DEST)bootstrapreduce$(COM) $(bootstrapreduce_OBJECTS) $(bootstrapreduce_LDADD) $(AM_LIBS) $(LIBS) $(CONAP)
	$(STRIP) bootstrapreduce$(COM)
endif gui
endif windows

fwindemo$(EXEEXT):	$(fwindemo_OBJECTS) $(fwindemo_DEPENDENCIES) $(FOXDEPS) $(WXDEPS) $(HDRS) $(FWINDEMOCOM) $(CYG_FWINDEMO)
	@printf "Relinking $@ because of $?\n"
if windows
if gui
	$(CP) $(cslbase)/fwindemo fwindemo-temp


@@ 1483,6 1497,7 @@ endif wx
if windows
if gui
fwindemo$(COM):	$(fwindemo_OBJECTS) $(fwindemo_DEPENDENCIES) $(FOXDEPS) $(WXDEPS) $(HDRS)
	@printf "Relinking $@ because of $?\n"
	$(CXXLINKTO) $(DEST)fwindemo$(COM ) $(fwindemo_OBJECTS) $(fwindemo_LDADD) $(AM_LIBS) $(LIBS) $(CONAP)
	$(STRIP) fwindemo$(COM)
endif gui


@@ 1490,6 1505,7 @@ endif windows

if wx
wxpsl$(EXEEXT):	$(wxpsl_OBJECTS) $(wxpsl_DEPENDENCIES) $(WXDEPS) $(WXPSLCOM)
	@printf "Relinking $@ because of $?\n"
	$(CXXLINKTO) $(DEST)wxpsl$(EXEEXT) $(wxpsl_OBJECTS) $(wxpsl_LDADD) $(AM_LIBS) $(LIBS) $(WINAP)
if !debug
if !darwin


@@ 1504,6 1520,7 @@ endif wx
if windows
if gui
wxpsl$(COM):	$(wxpsl_OBJECTS) $(wxpsl_DEPENDENCIES) $(WXDEPS)
	@printf "Relinking $@ because of $?\n"
	$(CXXLINKTO) $(DEST)wxpsl$(COM) $(wxpsl_OBJECTS) $(wxpsl_LDADD) $(AM_LIBS) $(LIBS) $(CONAP)
	$(STRIP) wxpsl$(COM)
endif gui


@@ 1512,6 1529,7 @@ endif windows
if fox

fontdemo$(EXEEXT): $(fontdemo_OBJECTS) $(fontdemo_DEPENDENCIES) $(FOXDEPS)
	@printf "Relinking $@ because of $?\n"
	$(CXXLINKTO) $(DEST)fontdemo$(EXEEXT) $(fontdemo_OBJECTS) $(fontdemo_LDADD) $(AM_LIBS) $(LIBS) $(CONAP)
if !debug
if !darwin


@@ 1520,6 1538,7 @@ endif !darwin
endif !debug

foxdemo$(EXEEXT): $(foxdemo_OBJECTS) $(foxdemo_DEPENDENCIES) $(FOXDEPS)
	@printf "Relinking $@ because of $?\n"
	$(CXXLINKTO) $(DEST)foxdemo$(EXEEXT) $(foxdemo_OBJECTS) $(foxdemo_LDADD) $(AM_LIBS) $(LIBS) $(CONAP)
if !debug
if !darwin


@@ 1528,6 1547,7 @@ endif !darwin
endif !debug

showmathdemo$(EXEEXT): $(showmathdemo_OBJECTS) $(showmathdemo_DEPENDENCIES) $(FOXDEPS)
	@printf "Relinking $@ because of $?\n"
	$(CXXLINKTO) $(DEST)showmathdemo$(EXEEXT) $(showmathdemo_OBJECTS) $(showmathdemo_LDADD) $(AM_LIBS) $(LIBS) $(CONAP)
if !debug
if !darwin


@@ 1712,6 1732,7 @@ $(plain_alloctest):	alloctest.exe
endif exeext

termdemo$(EXEEXT): $(termdemo_OBJECTS) $(termdemo_DEPENDENCIES)
	@printf "Relinking $@ because of $?\n"
	$(CXXLINKTO) $(DEST)termdemo$(EXEEXT) $(termdemo_LDADD) $(termdemo_OBJECTS) $(LIBS)
if !debug
if !darwin


@@ 2111,7 2132,7 @@ $(generated_objects):	$(gensrc)/make.stamp $(bootstrapreducedep) \
# I rather hope that the delay while C-code is re-created is long enough
# to let any previous steps complete.

$(generated_source_deps) &:	$(gensrc)/make.stamp $(HDRS)
$(generated_source_deps):	$(gensrc)/make.stamp $(HDRS)
	$(MAKE) standard_c_code

standard-c-code $(generated_lisp):	$(gensrc)/make.stamp $(HDRS)


@@ 2385,8 2406,9 @@ $(reduce)/bin/rfcsl$(EXEEXT): \
		$(generic)/newfront/redparent.c \
		$(generic)/newfront/redsig.c \
		$(generic)/newfront/redstrings.c $(HDRS)
	@printf "Relinking $@ because of $?\n"
	@printf "About to build redfront for %s\n" `pwd`; \
	cd ../redfront && $(MAKE)
	$(MAKE) -C ../redfront

endif redfront



@@ 2410,7 2432,6 @@ forinstall:	reduce$(EXEEXT) reduce.img \
if cygwin
install-exec-local:	reduce.exe reduce.img bootstrapreduce.exe bootstrapreduce.img csl.exe csl.img
	printf "Install for cygwin not implemented\n"
	exit 1

else !cygwin
if mac_framework


@@ 2470,19 2491,16 @@ endif !cygwin
if cygwin
uninstall-local:
	printf "Uninstall for cygwin not implemented\n"
	exit 1

else !cygwin
if mac_framework
uninstall-local:
	printf "Uninstall for macintosh not implemented\n"
	exit 1

else !mac_framework
if windows
uninstall-local:
	printf "Uninstall for windows not implemented\n"
	exit 1

else !windows
uninstall-local:

A web/htdocs/web-reduce/DerivativeTemplate.inc => web/htdocs/web-reduce/DerivativeTemplate.inc +140 -0
@@ 0,0 1,140 @@
<!--*- mhtml -*-->
<div class="modal fade" id="DerivativeTemplate" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="DerivativeTemplateLabel" aria-hidden="true">
	<div class="modal-dialog modal-dialog-centered">
		<div class="modal-content">
			<div class="modal-header">
				<h5 class="modal-title" id="DerivativeTemplateLabel">Evaluate a Multiple (Partial) Derivative</h5>
				<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
			</div>
			<div class="modal-body">
				<p class="center"><small>The dependent variable or expression and an independent variable are required. Other fields may be left empty. Superscripts represent orders, which unless empty must be positive integers.</small></p>
				<p class="center">
					<small>REDUCE Manual:&nbsp;
            <a href="/manual-lookup.php?DF%20Operator" target="_blank" title="Opens in a new tab.">DF Operator</a></small>
				</p>
				<div class="container-fluid">
					<div class="mx-auto" style="width: 200px;">
						<div class="center">
							∂<sup></sup>
							<input type="text" size="18" value="ws" />
						</div>
						<div class="center" style="margin-top: 5px; border-top: thin solid black; padding-top: 5px;">
							∂<input type="text" size="1" data-index="0" value="x" /><sup><input type="text" size="1" data-index="0" style="font-style: normal" /></sup>
							∂<input type="text" size="1" data-index="1" /><sup><input type="text" size="1" data-index="1" style="font-style: normal" /></sup>
							∂<input type="text" size="1" data-index="2" /><sup><input type="text" size="1" data-index="2" style="font-style: normal" /></sup>
						</div>
					</div>
				</div>
				<div class="modal-footer">
					<button type="button" class="btn btn-secondary me-auto" title="Reset to default inputs">Reset</button>
					<button type="button" class="btn btn-primary" title="Insert into input editor">Edit</button>
					<button type="button" class="btn btn-primary" title="Send to REDUCE">Evaluate</button>
					<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
				</div>
			</div>
		</div>
	</div>
</div>
<script>
	'use strict'; // Necesary to make functions defined in a block local
	// Block used for namespace control:
	{
		const content = document.querySelector("#DerivativeTemplate div[class^=container]");
		const totalOrdSup = content.querySelector("sup");
		const inputs = content.querySelectorAll("input");
		const depVarTextField = inputs[0];
		const indVarTextFields = [inputs[1], inputs[3], inputs[5]];
		const ordTextFields = [inputs[2], inputs[4], inputs[6]];
		const buttons = document.querySelectorAll("#DerivativeTemplate div.modal-footer > button");

		const orders = [1, 0, 0];

		function resetButtonAction() {
			totalOrdSup.innerText = "";
			depVarTextField.value = "ws";
			indVarTextFields[0].value = "x";
			indVarTextFields[1].value = indVarTextFields[2].value = "";
			ordTextFields[0].value = ordTextFields[1].value = ordTextFields[2].value = "";
			orders[0] = 1; orders[1] = orders[2] = 0;
		}
		resetButtonAction();
		buttons[0].addEventListener("click", resetButtonAction);

		const alertHeader = "Derivative Template Error\n";

		function indVarAction(event) {
			const n = event.target.dataset.index;
			const indVar = indVarTextFields[n].value.trim();
			let order = ordTextFields[n].value.trim();
			if (indVar.length == 0) {
				if (order.length > 0) {
					alert(alertHeader +
						"Specify the independent variable before specifying its order.");
					ordTextFields[n].value = "";
				}
				order = 0;
			} else if (order.length == 0)
				order = 1;
			else {
				order = Number(order);
				if (!Number.isInteger(order) || order <= 0) {
					alert(alertHeader +
						"An order must be a positive integer.");
					ordTextFields[n].value = "";
					return;
				}
			}
			orders[n] = order;
			order = orders.reduce((acc, val) => acc + val); // sum of orders
			if (order > 1) {
				totalOrdSup.innerText = order + " ";
			} else {
				totalOrdSup.innerText = "";
			}
		}

		for (let n = 0; n < 3; n++) {
			indVarTextFields[n].addEventListener("input", indVarAction);
			ordTextFields[n].addEventListener("input", indVarAction);
		}

		function result() {
			const depVar = depVarTextField.value.trim();
			let indVarFound = false;
			if (depVar.length == 0) {
				alert(alertHeader +
					"A dependent variable or expression is required.");
				throw new Error("empty field");
			}
			let text = "df(" + depVar;
			for (let n = 0; n < 3; n++) {
				const indVar = indVarTextFields[n].value.trim();
				if (indVar.length != 0) {
					indVarFound = true;
					text += ", " + indVar;
					if (orders[n] > 1) text += ", " + orders[n];
				}
			}
			if (indVarFound) return text + ")";
			else {
				alert(alertHeader +
					"At least one independent variable is required.");
				throw new Error("empty field");
			}
		}

		buttons[1].addEventListener("click", () => {
			try {
				inputTextArea.setRangeText(result());
				buttons[3].click();
			} catch (ignored) { }
		});

		buttons[2].addEventListener("click", () => {
			try {
				sendToReduceAndEcho(result() + ";");
				buttons[3].click();
			} catch (ignored) { }
		});
	}
</script>

A web/htdocs/web-reduce/ExpLogFunctions.inc => web/htdocs/web-reduce/ExpLogFunctions.inc +171 -0
@@ 0,0 1,171 @@
<!--*- mhtml -*-->
<div class="modal fade" id="ExpLogFunctions" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="ExpLogFunctionsLabel" aria-hidden="true">
	<div class="modal-dialog modal-dialog-centered modal-lg">
		<div class="modal-content">
			<div class="modal-header">
				<h5 class="modal-title" id="ExpLogFunctionsLabel">Exponentials, Logarithms, Powers, Roots, etc</h5>
				<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
			</div>
			<div class="modal-body">
				<p class="center"><small>Click on the radio button to the left of the function you want to use. Hover over a function for a brief description. Some functions do not simplify symbolically but evaluate numerically, as indicated in their tooltips.</small></p>
				<p class="center">
					<small>REDUCE Manual:&nbsp;
                        <a href="/manual-lookup.php?Mathematical%20Functions" target="_blank" title="Opens in a new tab.">Mathematical Functions</a>&nbsp;
                        <a href="/manual-lookup.php?SPECFN:" target="_blank" title="Opens in a new tab.">SPECFN Package</a></small>
				</p>
				<div class="container-fluid">
					<div class="row mb-4">
						<div class="col" title="Exponential: e^x = exp(x)">
							<input id="ExpRadioButton" type="radio" name="ExpLogRadioGroup" />
							<label for="ExpRadioButton">
								e <sup>
									<input id="ExpTextField" type="text" size="3" value="ws" /></sup></label>
						</div>
						<div class="col" title="Natural, i.e. base e, logarithm: ln e = 1">
							<input id="LnRadioButton" type="radio" name="ExpLogRadioGroup" />
							<label for="LnRadioButton">
								ln
                <input id="LnTextField" type="text" size="3" value="ws" /></label>
						</div>
						<div class="col" title="Logarithm to any base, e.g. 2">
							<input id="LogBaseRadioButton" type="radio" name="ExpLogRadioGroup" />
							<label for="LogBaseRadioButton">
								log<sub><input id="LogATextField" type="text" size="2" value="2" style="font-style: normal" /></sub>
								<input id="LogBTextField" type="text" size="3" value="ws" /></label>
						</div>
					</div>
					<div class="row mb-4">
						<div class="col" title="Power, e.g. x^2">
							<input id="PowerRadioButton" type="radio" name="ExpLogRadioGroup" />
							<label for="PowerRadioButton">
								<input id="PowATextField" type="text" size="3" value="ws" /></label>
							<sup>
								<input id="PowBTextField" type="text" size="2" value="2" style="font-style: normal" /></sup>
						</div>
						<div class="col" title="Square root">
							<input id="SqrtRadioButton" type="radio" name="ExpLogRadioGroup" />
							<label for="SqrtRadioButton">
								<span style="font-size: 150%">√</span>
								<input id="SqrtTextField" type="text" size="3" value="ws" /></label>
						</div>
						<div class="col" title="hypot(x,y) = √(x^2+y^2); numerical only!">
							<input id="HypotRadioButton" type="radio" name="ExpLogRadioGroup" />
							<label for="HypotRadioButton">
								hypot<span style="font-size: 150%">(</span><input id="HypotXTextField" type="text" size="3" value="x" />
								<span style="font-size: 150%">,</span>
								<input id="HypotYTextField" type="text" size="3" value="y" /><span style="font-size: 150%">)</span></label>
						</div>
					</div>
					<div class="row mb-4">
						<div class="col" title="Base 10 logarithm">
							<input id="Log10RadioButton" type="radio" name="ExpLogRadioGroup" />
							<label for="Log10RadioButton">
								log<sub>10</sub>
								<input id="Log10TextField" type="text" size="3" value="ws" /></label>
						</div>
						<div class="col" title="Any root, e.g. cube or third root: (n√x)^n = x">
							<input id="RootRadioButton" type="radio" name="ExpLogRadioGroup" />
							<label for="RootRadioButton">
								<sup>
									<input id="RootATextField" type="text" size="2" value="3" style="font-style: normal" /></sup>
								<span style="font-size: 150%">√</span>
								<input id="RootBTextField" type="text" size="3" value="ws" /></label>
						</div>
						<div class="col" title="Two argument version of atan(y/x) that returns an angle in the interval (-π/2, π/2] in the correct quadrant depending on the signs of x and y">
							<input id="Atan2RadioButton" type="radio" name="ExpLogRadioGroup" />
							<label for="Atan2RadioButton">
								atan<sub>2</sub><span style="font-size: 150%">(</span><input id="Atan2YTextField" type="text" size="3" value="y" />
								<span style="font-size: 150%">/</span>
								<input id="Atan2XTextField" type="text" size="3" value="x" /><span style="font-size: 150%">)</span></label>
						</div>
					</div>
					<div class="row">
						<div class="col" title="Factorial: n! = n×(n-1)×…×2×1">
							<input id="FactorialRadioButton" type="radio" name="ExpLogRadioGroup" />
							<label for="FactorialRadioButton">
								<input id="FactorialTextField" type="text" size="3" value="ws" />
								<span style="font-size: 150%">!</span></label>
						</div>
						<div class="col" title="Binomial coefficient: (n over m) = n!/((n-m)!m!)">
							<input id="BinCoeffRadioButton" type="radio" name="ExpLogRadioGroup" />
							<label for="BinCoeffRadioButton">
								<span style="font-size: 400%; vertical-align: middle">(</span>
								<span style="display: inline-block; vertical-align: middle">
									<input id="BinCoeffNTextField" type="text" size="3" value="n" /><br />
									<input id="BinCoeffMTextField" type="text" size="3" value="m" />
								</span>
								<span style="font-size: 400%; vertical-align: middle">)</span>
							</label>
						</div>
						<!-- This needs better handling of the load_package and a manual link to trigd. -->
<!--						<div class="col" title="Two argument version of atand(y/x) that returns an angle in the interval (-90°, 90°] in the correct quadrant depending on the signs of x and y">
							<input id="Atan2DRadioButton" type="radio" name="ExpLogRadioGroup" />
							<label for="Atan2DRadioButton">
								atan<sub>2</sub>d<span style="font-size: 150%">(</span><input id="Atan2DYTextField" type="text" size="3" value="y" />
								<span style="font-size: 150%">/</span>
								<input id="Atan2DXTextField" type="text" size="3" value="x" /><span style="font-size: 150%">)</span></label>
						</div>-->
					</div>
				</div>
				<div class="modal-footer">
					<button type="button" class="btn btn-secondary me-auto" title="Reset to default inputs" style="display: none">Reset</button><!-- Not yet implemented -->
					<button type="button" class="btn btn-primary" title="Insert into input editor">Edit</button>
					<button type="button" class="btn btn-primary" title="Send to REDUCE">Evaluate</button>
					<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
				</div>
			</div>
		</div>
	</div>
</div>

<script>
	'use strict'; // Necesary to make functions defined in a block local
	// Block used for namespace control:
	{
		const buttons = document.querySelectorAll("#ExpLogFunctions div.modal-footer > button");

		function getTextFieldValue(elementId) {
			return document.getElementById(elementId).value.trim();
		}

		function result() {
			switch (document.querySelector('input[name="ExpLogRadioGroup"]:checked').id) {
				case "ExpRadioButton":
					return `exp(${getTextFieldValue("ExpTextField")})`;
				case "LnRadioButton":
					return `log(${getTextFieldValue("LnTextField")})`;
				case "LogBaseRadioButton":
					return `logb(${getTextFieldValue("LogBTextField")},${getTextFieldValue("LogATextField")})`;
				case "PowerRadioButton":
					return `(${getTextFieldValue("PowATextField")})^(${getTextFieldValue("PowBTextField")})`;
				case "SqrtRadioButton":
					return `sqrt(${getTextFieldValue("SqrtTextField")})`;
				case "HypotRadioButton":
					return `hypot(${getTextFieldValue("HypotXTextField")},${getTextFieldValue("HypotYTextField")})`;
				case "Log10RadioButton":
					return `log10(${getTextFieldValue("Log10TextField")})`;
				case "RootRadioButton":
					return `(${getTextFieldValue("RootBTextField")})^1/(${getTextFieldValue("RootATextField")})`;
				case "Atan2RadioButton":
					return `atan2(${getTextFieldValue("Atan2YTextField")},${getTextFieldValue("Atan2XTextField")})`;
				case "FactorialRadioButton":
					return `factorial(${getTextFieldValue("FactorialTextField")})`;
				case "BinCoeffRadioButton":
					return `binomial(${getTextFieldValue("BinCoeffNTextField")},${getTextFieldValue("BinCoeffMTextField")})`;
				case "Atan2DRadioButton":
					//loadPackage("trigd"); // This fails because it needs to wait for REDUCE to digest it!
					return `load_package trigd; atan2d(${getTextFieldValue("Atan2DYTextField")},${getTextFieldValue("Atan2DXTextField")})`;
			}
		}

		buttons[1].addEventListener("click", () => {
			inputTextArea.setRangeText(result());
			buttons[3].click();
		});

		buttons[2].addEventListener("click", () => {
			sendToReduceAndEcho(result() + ";");
			buttons[3].click();
		});
	}
</script>

M web/htdocs/web-reduce/InputEditor.js => web/htdocs/web-reduce/InputEditor.js +9 -6
@@ 5,6 5,7 @@ let inputListIndex = 0;
let maxInputListIndex = -1;

function sendInput(event) {
    if (noOutput) return; // REDUCE not yet loaded!
    // Strip trailing white space from the input:
    let text = inputTextArea.value.replace(/\s+^/, "");
    if (text.length > 0) {


@@ 26,7 27,8 @@ function sendInput(event) {

sendButton.addEventListener('click', sendInput);

function earlierInput() {
function earlierInput(event) {
    event.preventDefault();
    if (inputListIndex > 0) {
        inputTextArea.value = inputList[--inputListIndex];
        if (inputListIndex <= maxInputListIndex)


@@ 34,12 36,13 @@ function earlierInput() {
    }
    if (inputListIndex == 0)
        earlierButton.disabled = true;
    inputTextArea.focus()
    inputTextArea.focus();
}

earlierButton.addEventListener('click', earlierInput);

function laterInput() {
function laterInput(event) {
    event.preventDefault();
    if (inputListIndex < maxInputListIndex) {
        inputTextArea.value = inputList[++inputListIndex];
    } else {


@@ 52,7 55,7 @@ function laterInput() {
    if (inputListIndex > maxInputListIndex) {
        laterButton.disabled = true;
    }
    inputTextArea.focus()
    inputTextArea.focus();
}

laterButton.addEventListener('click', laterInput);


@@ 64,10 67,10 @@ inputTextArea.addEventListener("keydown", event => {
                sendInput(event);
                break;
            case "ArrowUp":
                earlierInput();
                earlierInput(event);
                break;
            case "ArrowDown":
                laterInput();
                laterInput(event);
        }
    }
});

A web/htdocs/web-reduce/IntegralTemplate.inc => web/htdocs/web-reduce/IntegralTemplate.inc +143 -0
@@ 0,0 1,143 @@
<!--*- mhtml -*-->
<div class="modal fade" id="IntegralTemplate" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="IntegralTemplateLabel" aria-hidden="true">
	<div class="modal-dialog modal-dialog-centered modal-lg">
		<div class="modal-content">
			<div class="modal-header">
				<h5 class="modal-title" id="IntegralTemplateLabel">Evaluate a Multiple Integral</h5>
				<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
			</div>
			<div class="modal-body">
				<p class="center"><small>The integrand and first integration variable are required. Enter additional integration variables to create a double or triple integral and display additional ∫ and d symbol pairs, which nest and their colours show how they match. Each pair of limits must be both empty, giving an indefinite integral, or both specified, giving a definite integral.</small></p>
				<p class="center">
					<small>REDUCE Manual:&nbsp;
            <a href="/manual-lookup.php?INT%20Operator" target="_blank" title="Opens in a new tab.">INT Operator</a></small>
				</p>
				<div class="container-fluid" style="text-align: center">
					<div style="display: inline-block; vertical-align: middle">
						<input type="text" size="3" /><br />
						<span style="font-size: 300%; color: red">∫</span><br />
						<input type="text" size="3" />
					</div>
					<div style="display: inline-block; vertical-align: middle">
						<input type="text" size="3" /><br />
						<span style="font-size: 300%; color: green">∫</span><br />
						<input type="text" size="3" />
					</div>
					<div style="display: inline-block; vertical-align: middle">
						<input type="text" size="3" /><br />
						<span style="font-size: 300%; color: blue">∫</span><br />
						<input type="text" size="3" />
					</div>
					<input type="text" size="25" value="ws" />
					<span style="color: blue">d</span>
					<input type="text" size="1" value="x" />&nbsp;
					<span style="color: green">d</span>
					<input type="text" size="1" />&nbsp;
					<span style="color: red">d</span>
					<input type="text" size="1" />
				</div>
				<div class="modal-footer">
					<button type="button" class="btn btn-secondary me-auto" title="Reset to default inputs">Reset</button>
					<button type="button" class="btn btn-primary" title="Insert into input editor">Edit</button>
					<button type="button" class="btn btn-primary" title="Send to REDUCE">Evaluate</button>
					<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
				</div>
			</div>
		</div>
	</div>
</div>
<script>
	'use strict'; // Necesary to make functions defined in a block local
	// Block used for namespace control:
	{
		const contents = document.querySelector("#IntegralTemplate div[class^=container]").children;
		const zIntDiv = contents[0];
		const zLimTextFields = zIntDiv.querySelectorAll("input"); // [upper, lower]
		const yIntDiv = contents[1];
		const yLimTextFields = yIntDiv.querySelectorAll("input");
		const xIntDiv = contents[2];
		const xLimTextFields = xIntDiv.querySelectorAll("input");
		const limTextFields = [xLimTextFields, yLimTextFields, zLimTextFields];
		const integrandTextField = contents[3];
		const intVarTextFields = [contents[5], contents[7], contents[9]];
		const yDSpan = contents[6];
		const zDSpan = contents[8];
		const buttons = document.querySelectorAll("#IntegralTemplate div.modal-footer > button");

		function resetButtonAction() {
			for (let i = 0; i < 3; i++)
				limTextFields[i][0].value = limTextFields[i][1].value = "";
			zIntDiv.hidden = yIntDiv.hidden = true;
			integrandTextField.value = "ws";
			intVarTextFields[0].value = "x";
			intVarTextFields[1].value = intVarTextFields[2].value = "";
			yDSpan.hidden = zDSpan.hidden = true;
			intVarTextFields[1].hidden = false;
			intVarTextFields[2].hidden = true;
		}
		resetButtonAction();
		buttons[0].addEventListener("click", resetButtonAction);

		const alertHeader = "Integral Template Error\n";

		intVarTextFields[0].addEventListener("input", () => {
			intVarTextFields[1].hidden = (intVarTextFields[0].value.trim().length == 0);
		});

		intVarTextFields[1].addEventListener("input", () => {
			const hidden = (intVarTextFields[1].value.trim().length == 0);
			yIntDiv.hidden = hidden;
			yDSpan.hidden = hidden;
			intVarTextFields[2].hidden = hidden;
		});

		intVarTextFields[2].addEventListener("input", () => {
			const hidden = (intVarTextFields[2].value.trim().length == 0);
			zIntDiv.hidden = hidden;
			zDSpan.hidden = hidden;
		});

		function result() {
			let text = integrandTextField.value.trim();
			if (text.length == 0 || intVarTextFields[0].value.trim().length == 0) {
				alert(alertHeader +
					"The integrand and integration variable are both required.");
				throw new Error("empty field");
			}
			// Wrap integrand in nested integrals:
			for (let i = 0; i < 3; i++) {
				const intVar = intVarTextFields[i].value.trim();
				if (intVar.length != 0) {
					text = "int(" + text;
					const lowLim = limTextFields[i][0].value.trim();
					const upLim = limTextFields[i][1].value.trim();
					const indefInt = (lowLim.length == 0);
					if ((indefInt) != (upLim.length == 0)) {
						// boolean != is equivalent to exclusive or.
						alert(alertHeader +
							"The limits must be both empty or both specified.");
						throw new Error("empty field");
					}
					text += ", " + intVar;
					if (!indefInt) text += ", " + lowLim + ", " + upLim;
					text += ")";
				}
			}
			return text;
		}

		buttons[1].addEventListener("click", () => {
			try {
				inputTextArea.setRangeText(result());
				buttons[3].click();
			} catch (ignored) { }
		});

		buttons[2].addEventListener("click", () => {
			try {
				sendToReduceAndEcho(result() + ";");
				buttons[3].click();
			} catch (ignored) { }
		});
	}
</script>

R web/htdocs/web-reduce/index.html => web/htdocs/web-reduce/Main.js +139 -179
@@ 1,189 1,149 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <link rel="icon" type="image/ico" href="/favicon.ico" />
    <link href="StyleSheet.css" rel="stylesheet" type="text/css" />
    <title>Web REDUCE</title>
    <style type="text/css">
        #OutputDiv, textarea {
            width: 100%
        }
// Main source code

        #OutputDiv {
            border: medium #000000 solid;
            height: 20em;
            resize: both;
            overflow: auto;
        }
// Set up access to document elements and reset their defaults for when the page is reloaded:
const typesetMathsCheckbox = document.getElementById('TypesetMathsCheckbox');
typesetMathsCheckbox.disabled = true;
const centreTypesetMathsCheckbox = document.getElementById('CentreTypesetMathsCheckbox');
const outputDiv = document.getElementById('OutputDiv');
const inputTextArea = document.getElementById('InputTextArea');
inputTextArea.value = "";
inputTextArea.focus();
const earlierButton = document.getElementById('EarlierButton');
earlierButton.disabled = true;
const sendButton = document.getElementById('SendButton');
sendButton.disabled = true;
const laterButton = document.getElementById('LaterButton');
laterButton.disabled = true;
let noOutput = true, hideOutput = false;

            #OutputDiv > pre {
                margin: 0;
            }
// Only for use during development:
//throw new Error("Don't run REDUCE!");

        .buttons {
            display: flex;
            justify-content: space-evenly;
        }
    </style>
    <script>
        MathJax = {
            tex: {
                macros: { "*": "\\," }
            }
        };
    </script>
    <script async="async" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"></script>
</head>
<body>
    <h1>Web REDUCE</h1>
    <p class="center">
        <a href="about.html" target="_blank" title="Opens in a new tab.">About Web REDUCE</a>
    </p>
    <p class="center">
        <label for="TypesetMathsCheckbox">Typeset Maths</label><input id="TypesetMathsCheckbox" type="checkbox" checked="checked" disabled="disabled" />
        &emsp;
        <label for="CentreTypesetMathsCheckbox">Centre Typeset Maths</label><input id="CentreTypesetMathsCheckbox" type="checkbox" checked="checked" />
    </p>
    <p>
        <label for="OutputDiv"><strong>Input/Output Display</strong></label>
    </p>
    <div id="OutputDiv">
        REDUCE is loading. Please wait...
    </div>
    <p>
        <label for="InputTextArea"><strong>Input Editor</strong></label>
    </p>
    <textarea id="InputTextArea" rows="5"></textarea>
    <p class="buttons">
        <button id="EarlierButton" type="button" disabled="disabled" title="Select earlier keyboard input. Keyboard Shortcut: Control+UpArrow.">▲ Earlier Input</button>
        <button id="SendButton" type="button" title="Send the input above to REDUCE, terminating with a semicolon if necessary. Keyboard Shortcut: Control+Enter. (Also hold Shift to prevent auto-termination.)">Send Input</button>
        <button id="LaterButton" type="button" disabled="disabled" title="Select later keyboard input. Keyboard Shortcut: Control+DownArrow.">▼ Later Input</button>
    </p>
    <a href="https://www.mathjax.org">
        <img title="Powered by MathJax"
            src="https://www.mathjax.org/badge/badge.gif"
            alt="Powered by MathJax" style="float: right" />
    </a>
    <script>
        // Set up access to document elements and reset their defaults for when the page is reloaded:
        const typesetMathsCheckbox = document.getElementById('TypesetMathsCheckbox');
        const centreTypesetMathsCheckbox = document.getElementById('CentreTypesetMathsCheckbox');
        const outputDiv = document.getElementById('OutputDiv');
        const inputTextArea = document.getElementById('InputTextArea');
        inputTextArea.value = "";
        inputTextArea.focus();
        const earlierButton = document.getElementById('EarlierButton');
        earlierButton.disabled = true;
        const sendButton = document.getElementById('SendButton');
        const laterButton = document.getElementById('LaterButton');
        laterButton.disabled = true;
        let noOutput = true, hideOutput = false;

        /**
         * Scroll the REDUCE output to the bottom.
         * A sufficient delay seems necessary for the input to be rendered!
         * This may not be the best solution but it seems to work provided the delay is long enough.
         */
        function scrollOutputDivToBottom() {
            setTimeout(function () { outputDiv.lastChild.scrollIntoView(false) }, 300);
            // Scroll again after KaTeX has had time to complete:
            setTimeout(function () { outputDiv.lastChild.scrollIntoView(false) }, 1000);
/**
 * Scroll the REDUCE output to the bottom.
 * A sufficient delay seems necessary for the input to be rendered!
 * This may not be the best solution but it seems to work provided the delay is long enough.
 */
function scrollOutputDivToBottom() {
    setTimeout(function () { outputDiv.lastChild.scrollIntoView(false) }, 300);
    // Scroll again after KaTeX has had time to complete:
    setTimeout(function () { outputDiv.lastChild.scrollIntoView(false) }, 1000);
}

function sendToOutputDiv(text) {
    if (noOutput) {
        // This code executes immediately after REDUCE loads:
        outputDiv.textContent = "";
        sendButton.disabled = false;
        typesetMathsCheckbox.disabled = false;
        noOutput = false;
    }
    // For now, append text within a <pre> element:
    const pre = document.createElement("pre");
    pre.innerText = text;
    outputDiv.appendChild(pre);
    scrollOutputDivToBottom();
}

const worker = new Worker('./reduce.web.js');
worker.onmessage = function (event) {
    if (hideOutput) { // hide changes of switches etc.
        hideOutput = false;
        return;
    }
    if (event.data.channel === 'stdout') {
        let output = event.data.line;
        // If an empty string is passed (ie asking for a blank line of output)
        // it gets lost in the display, so output a single space character.
        if (output == '') {
            console.log("OUTPUT: Empty line"); // for debugging
            sendToOutputDiv(' ');
            return;
        }
        console.log(`OUTPUT: ${output}`); // for debugging

        // The mathematical part of the output delived by REDUCE will have the form:
        // latex:\black$\displaystyle\frac{-2\*\arctan\left(x\right)+\log\left(x-1\right)-\log\left(x+1\right)}{4}$
        // delimited by a pair of control characters, \u0002 before and \u0005 after.
        // The TeX in the middle is extracted and MathJax formats it.

        function sendToOutputDiv(text) {
            if (noOutput) {
                outputDiv.textContent = "";
                typesetMathsCheckbox.disabled = false;
                noOutput = false;
            }
            // For now, append text within a <pre> element:
            const pre = document.createElement("pre");
            pre.innerText = text;
            outputDiv.appendChild(pre);
        // Detect the case where the output line contains some TeX:
        let n = output.indexOf('\u0002');
        if (n != -1) {
            // Here I not only strip out the material before the "U+0002" but also the
            // "junk" that reads "latex:\black$\displaystyle" and a final "U+0005".
            // Those are fragments that the REDUCE interface for TeXmacs inserts.
            output = output.substring(n + 1 + 26, output.length - 2);
            outputDiv.appendChild(MathJax.tex2chtml(output));
            // The MathJax documentation doesn't tell the whole story!
            // See https://github.com/mathjax/MathJax/issues/2365:
            MathJax.startup.document.clear();
            MathJax.startup.document.updateDocument();
            scrollOutputDivToBottom();
        }

        const worker = new Worker('./reduce.web.js');
        worker.onmessage = function (event) {
            if (hideOutput) { // hide changes of switches etc.
                hideOutput = false;
                return;
            }
            if (event.data.channel === 'stdout') {
                let output = event.data.line;
                // If an empty string is passed (ie asking for a blank line of output)
                // it gets lost in the display, so output a single space character.
                if (output == '') {
                    console.log("OUTPUT: Empty line"); // for debugging
                    sendToOutputDiv(' ');
                    return;
                }
                console.log(`OUTPUT: ${output}`); // for debugging

                // The mathematical part of the output delived by REDUCE will have the form:
                // latex:\black$\displaystyle\frac{-2\*\arctan\left(x\right)+\log\left(x-1\right)-\log\left(x+1\right)}{4}$
                // delimited by a pair of control characters, \u0002 before and \u0005 after.
                // The TeX in the middle is extracted and MathJax formats it.

                // Detect the case where the output line contains some TeX:
                let n = output.indexOf('\u0002');
                if (n != -1) {
                    // Here I not only strip out the material before the "U+0002" but also the
                    // "junk" that reads "latex:\black$\displaystyle" and a final "U+0005".
                    // Those are fragments that the REDUCE interface for TeXmacs inserts.
                    output = output.substring(n + 1 + 26, output.length - 2);
                    outputDiv.appendChild(MathJax.tex2chtml(output));
                    // The MathJax documentation doesn't tell the whole story!
                    // See https://github.com/mathjax/MathJax/issues/2365:
                    MathJax.startup.document.clear();
                    MathJax.startup.document.updateDocument();
                    scrollOutputDivToBottom();
                }
                else {
                    // Textual rather than mathematical output from REDUCE gets inserted as is.
                    sendToOutputDiv(output);
                }
            }
        else {
            // Textual rather than mathematical output from REDUCE gets inserted as is.
            sendToOutputDiv(output);
        }
    }
}

        function sendToReduce(str) {
            console.log(` INPUT: ${str}`); // for debugging
            // This function posts a string to REDUCE, which treats it rather as if
            // it had been keyboard input. At the start of a run I use this to send a
            // sequence of commands to REDUCE to adjust its input and output processing
            // to suit the needs I have here.
            const buf = new Uint8Array(str.length + 1);
            for (let i = 0; i < str.length; i++)
                buf[i] = str.charCodeAt(i);
            buf[str.length] = 0; // null-terminate for benefit of C/C++.
            worker.postMessage({
                funcName: 'insert_buffer',
                callbackId: '',
                data: buf
            });
        }
function sendToReduce(str) {
    console.log(` INPUT: ${str}`); // for debugging
    // This function posts a string to REDUCE, which treats it rather as if
    // it had been keyboard input. At the start of a run I use this to send a
    // sequence of commands to REDUCE to adjust its input and output processing
    // to suit the needs I have here.
    const buf = new Uint8Array(str.length + 1);
    for (let i = 0; i < str.length; i++)
        buf[i] = str.charCodeAt(i);
    buf[str.length] = 0; // null-terminate for benefit of C/C++.
    worker.postMessage({
        funcName: 'insert_buffer',
        callbackId: '',
        data: buf
    });
}

sendToReduce('<< lisp (!*redefmsg := nil); load_package tmprint;' +
    ' on nat; on fancy; off int >>$');

// *****************
// Utility Functions
// *****************

function sendToReduceAndEcho(text) {
    sendToOutputDiv(text);
    sendToReduce(text);
}

const loadedPackages = new Set(); // should probably be in a closure!

/**
 * Load the specified package once only.
 * @param {any} package
 */
function loadPackage(package) {
    if (loadedPackages.has(package)) return;
    sendToReduceAndEcho(`load_package ${package};`);
    // Need to wait for REDUCE to digest this.
    // Should wait for the next prompt, but can't at present, so...
    // ...
    loadedPackages.add(package);
}

// ****************
// User Interaction
// ****************

typesetMathsCheckbox.addEventListener("change", event => {
    hideOutput = true;
    if (typesetMathsCheckbox.checked) sendToReduce('on fancy;');
    else sendToReduce('off fancy;');
});

        sendToReduce('<< lisp (!*redefmsg := nil); load_package tmprint;' +
            ' on nat; on fancy; off int >>$');

        // ****************
        // User Interaction
        // ****************

        typesetMathsCheckbox.addEventListener("change", event => {
            hideOutput = true;
            if (typesetMathsCheckbox.checked) sendToReduce('on fancy;');
            else sendToReduce('off fancy;');
        });

        centreTypesetMathsCheckbox.addEventListener("change", event => {
            if (centreTypesetMathsCheckbox.checked) MathJax.config.chtml.displayAlign = 'center';
            else MathJax.config.chtml.displayAlign = 'left';
            MathJax.startup.getComponents(); // See http://docs.mathjax.org/en/latest/web/configuration.html
        });
    </script>
    <script src="InputEditor.js"></script>
</body>
</html>
centreTypesetMathsCheckbox.addEventListener("change", event => {
    if (centreTypesetMathsCheckbox.checked) MathJax.config.chtml.displayAlign = 'center';
    else MathJax.config.chtml.displayAlign = 'left';
    MathJax.startup.getComponents(); // See http://docs.mathjax.org/en/latest/web/configuration.html
});

A web/htdocs/web-reduce/index.php => web/htdocs/web-reduce/index.php +142 -0
@@ 0,0 1,142 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous" />
    <link href="StyleSheet.css" rel="stylesheet" type="text/css" />
    <title>Web REDUCE</title>
    <style type="text/css">
        #OutputDiv, textarea {
            width: 100%
        }

        #OutputDiv {
            border: medium black solid;
            height: 25em;
            resize: both;
            overflow: auto;
        }

            #OutputDiv > pre {
                margin: 0;
            }

        div.labelling {
            margin-top: 5px;
            margin-bottom: 2px;
        }

        #buttons {
            margin-top: 5px;
            margin-bottom: 10px;
            display: flex;
            justify-content: space-evenly;
        }

        div.modal-body input[type="text"] {
            text-align: center;
            font-style: italic;
        }
    </style>
    <script>
        MathJax = {
            tex: {
                macros: { "*": "\\," }
            }
        };
    </script>
    <script async="async" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"></script>
</head>
<body>
    <h1>Web REDUCE</h1>

    <!-- Menu bar -->
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <div class="container-fluid">
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarNavDropdown">
                <ul class="navbar-nav">
                    <!-- View Menu -->
                    <li class="nav-item dropdown">
                        <a class="nav-link dropdown-toggle" href="#" id="ViewMenuLink" role="button" data-bs-toggle="dropdown" aria-expanded="false">View
                        </a>
                        <ul class="dropdown-menu" aria-labelledby="ViewMenuLink">
                            <li class="dropdown-item">
                                <input id="TypesetMathsCheckbox" type="checkbox" checked="checked" disabled="disabled" />
                                <label for="TypesetMathsCheckbox">Typeset Maths</label>
                            </li>
                            <li class="dropdown-item">
                                <input id="CentreTypesetMathsCheckbox" type="checkbox" checked="checked" />
                                <label for="CentreTypesetMathsCheckbox">Centre Typeset Maths</label>
                            </li>
                        </ul>
                    </li>
                    <!-- Templates Menu -->
                    <li class="nav-item dropdown">
                        <a class="nav-link dropdown-toggle" href="#" id="TemplatesMenuLink" role="button" data-bs-toggle="dropdown" aria-expanded="false">Templates
                        </a>
                        <ul class="dropdown-menu" aria-labelledby="TemplatesMenuLink">
                            <li><a class="dropdown-item" href="#" data-bs-toggle="modal" data-bs-target="#DerivativeTemplate">Derivative...</a></li>
                            <li><a class="dropdown-item" href="#" data-bs-toggle="modal" data-bs-target="#IntegralTemplate">Integral...</a></li>
                        </ul>
                    </li>
                    <!-- Functions Menu -->
                    <li class="nav-item dropdown">
                        <a class="nav-link dropdown-toggle" href="#" id="FunctionsMenuLink" role="button" data-bs-toggle="dropdown" aria-expanded="false">Functions
                        </a>
                        <ul class="dropdown-menu" aria-labelledby="FunctionsMenuLink">
                            <li><a class="dropdown-item" href="#" data-bs-toggle="modal" data-bs-target="#ExpLogFunctions">Exp, Log, Power, Root, etc...</a></li>
                        </ul>
                    </li>
                    <!-- Help Menu -->
                    <li class="nav-item dropdown">
                        <a class="nav-link dropdown-toggle" href="#" id="HelpMenuLink" role="button" data-bs-toggle="dropdown" aria-expanded="false">Help
                        </a>
                        <ul class="dropdown-menu" aria-labelledby="HelpMenuLink">
                            <li><a class="dropdown-item" href="about.html" target="_blank" title="Opens in a new tab.">About Web REDUCE</a></li>
                            <li><a class="dropdown-item disabled" href="#">Web REDUCE User Guide</a></li>
                            <li><a class="dropdown-item" href="/manual/manual.html" target="_blank" title="Opens in a new tab.">REDUCE Manual</a></li>
                        </ul>
                    </li>
                </ul>
            </div>
        </div>
    </nav>

    <div class="labelling">
        <label for="OutputDiv"><strong>Input/Output Display</strong></label>
    </div>
    <div id="OutputDiv">
        REDUCE is loading. Please wait...
    </div>
    <div class="labelling">
        <label for="InputTextArea"><strong>Input Editor</strong></label>
    </div>
    <textarea id="InputTextArea" rows="3"></textarea>
    <div id="buttons">
        <button id="EarlierButton" type="button" disabled="disabled" title="Select earlier keyboard input. Keyboard Shortcut: Control+UpArrow.">▲ Earlier Input</button>
        <button id="SendButton" type="button" title="Send the input above to REDUCE, terminating with a semicolon if necessary. Keyboard Shortcut: Control+Enter. (Also hold Shift to prevent auto-termination.)">Send Input</button>
        <button id="LaterButton" type="button" disabled="disabled" title="Select later keyboard input. Keyboard Shortcut: Control+DownArrow.">▼ Later Input</button>
    </div>
    <a href="https://www.mathjax.org">
        <img title="Powered by MathJax"
            src="https://www.mathjax.org/badge/badge.gif"
            alt="Powered by MathJax" style="float: right" />
    </a>

    <script src="Main.js"></script>
    <script src="InputEditor.js"></script>
    <!-- Bootstrap JavaScript bundle with Popper -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
            integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>

    <!-- Modal Dialogues -->
    <?php include './DerivativeTemplate.inc' ?>
    <?php include './IntegralTemplate.inc' ?>
    <?php include './ExpLogFunctions.inc' ?>
</body>
</html>