Merge branch 'next-release'

This commit is contained in:
Jeff Parsons 2017-01-03 15:39:26 -08:00
commit 9a90cdbfb0
7 changed files with 246 additions and 19 deletions

View file

@ -64,10 +64,10 @@ was add the following Front Matter to the top of the Markdown file:
and then embed the machines in the post, each with a single line:
{% raw %}
{% raw %}
{% include machine.html id="ibm5170" %}
{% include machine.html id="vt100" %}
{% endraw %}
{% endraw %}
For people rolling their own web pages, [the basics](/docs/pcx86/) haven't changed, and adding a serial connection merely
requires adding a *connection* property (eg, `connection:"com2->vt100.serialPort"`) to the *parms* parameter passed to the

View file

@ -24,9 +24,9 @@ beginning of the PDP-11 line: the PDP-11/20.
I'm starting with the top-of-the-line PDP-11/70 largely because the core of the emulator is being adapted from the
JavaScript [PDP-11/70 Emulator (v1.3)](http://skn.noip.me/pdp11/pdp11.html) written by
Paul Nankervis, who has generously given permission to use his code in PCjs. Since his emulator is a fully functional
11/70, it made sense to start there and work backwards, factoring out features as needed.
11/70, it made sense to start there and work backwards, disabling features according to the model.
The code has already undergone a lot of refactoring. Opcodes are now decoded by function tables rather than a single
The code has already undergone a lot of refactoring. Opcodes are now decoded by function tables rather than a single
switch statement, and every opcode is implemented with a discrete function. Other refactoring includes flag management,
interrupt management, and device management.
@ -37,9 +37,9 @@ component, [device.js](/modules/pdp11/lib/device.js), which has now been convert
The first new device component is [serial.js](/modules/pdp11/lib/serial.js), which is currently the
only means PDPjs has of communicating with the outside world. So you can try
[PDPjs connected to a VT100 Terminal](/devices/pdp11/machine/1170/vt100/), by clicking the **Run** button on the test machine.
The test machine is running [custom boot code](/apps/pdp11/boot/test/), adapted from boot code written by Paul, but due to the
lack of other device support, nothing can be booted yet.
[PDPjs connected to a VT100 Terminal](/devices/pdp11/machine/1170/vt100/), by clicking the **Run** button on the
test machine. The test machine is running a custom [Boot Monitor](/apps/pdp11/boot/monitor/) included with
[Paul's emulator](http://skn.noip.me/pdp11/), but due to the lack of other device support, nothing can be booted yet.
Obviously PDPjs is very much a work-in-progress. Before I proceed much farther, I really want to put the CPU through
some rigorous testing, so I'll be on the lookout for some comprehensive PDP-11 instruction tests. Or I'll write my own,

View file

@ -0,0 +1,61 @@
---
layout: post
title: PDP-11 Tutorials
date: 2017-01-03 15:00:00
permalink: /blog/2017/01/03/
machines:
- id: test1170
type: pdp11
debugger: true
config: /devices/pdp11/machine/1170/vt100/debugger/machine.xml
connection: dl11->vt100.serialPort
- id: vt100
type: pc8080
config: /devices/pc8080/machine/vt100/machine.xml
connection: serialPort->test1170.dl11
sticky: top
---
Introducing PDP-11 tutorials. For more information, keep scrolling.
{% include machine.html id="vt100" %}
[PDPjs](/devices/pdp11/machine/) is able to run a variety of old DEC operating systems, such as RT-11 and RSTS/E,
and while there are manuals available online, thanks to the efforts of those who operate and contribute to websites
like [bitsavers.org](http://bitsavers.org), I suspect most people don't have a lot of interest or time to spend
reading old manuals.
In an effort to remedy that situation, I'm adding some new features to PCjs. The first feature is what I call
"Sticky Machines", and it's more a website feature than a machine feature. At the top of any PCjs webpage, in the
*machines* section, a machine can now have a *sticky* property. For now, the only supported value is "top"; e.g.:
machines:
- id: vt100
type: pc8080
config: /devices/pc8080/machine/vt100/machine.xml
connection: serialPort->test1170.dl11
sticky: top
A sticky machine makes it easier to construct a tutorial page for a single machine, by preventing that machine from
scrolling off the top of the page; it "sticks" to the top instead. The rest of the page scrolls normally, allowing the
user to progress at their own pace through the text and/or images of an accompanying tutorial.
The second feature is a generalized method for sending commands to components within a machine. For example, if we
want to send some keyboard commands to machine:
{% raw %}
{% include machine-command.html type='button' label='Try It!' machine='vt100' component='Keyboard' command='sendString' value='Hello World' %}
{% endraw %}
which should translate into a control that looks like:
<button type="button" onclick="commandMachine('vt100','Keyboard','sendString','Hello World')">Try It!</button>
In fact, let's try it now. {% include machine-command.html type='button' label='Try It!' machine='vt100' component='Keyboard' command='sendString' value='Hello World' %}
Obviously, every component we want to control will need to be updated to export the necessary functions.
{% include machine.html id="test1170" %}
*[@jeffpar](http://twitter.com/jeffpar)*
*Jan 3, 2017*

View file

@ -879,6 +879,7 @@ HTMLOut.prototype.findTokens = function(reTokens)
aParms.push(aMatch[1]);
}
}
//noinspection JSUnresolvedFunction
fnToken.call(this, sToken, sIndent, aParms);
}
}
@ -1968,6 +1969,12 @@ HTMLOut.prototype.processMachines = function(aMachines, buildOptions, done)
}
}
this.addFilesToHTML(asFiles, sScriptEmbed);
if (infoMachine['sticky']) {
asFiles = [];
asFiles.push("/modules/shared/lib/sticky.js");
sScriptEmbed = '<script type="text/javascript">addStickyMachine("' + infoMachine['id'] + '")</script>';
this.addFilesToHTML(asFiles, sScriptEmbed);
}
if (buildOptions.id) {
asFiles = [];
asFiles.push("/modules/build/lib/build.js");

View file

@ -367,7 +367,7 @@ MarkOut.aFMBooleanMachineProps = {
'autopower': "autoPower",
'autostart': "autoStart"
};
MarkOut.aFMReservedMachineProps = ['id', 'name', 'type', 'debugger', 'config', 'template', 'uncompiled', 'autoMount', 'drives', 'parms'];
MarkOut.aFMReservedMachineProps = ['id', 'name', 'type', 'debugger', 'config', 'template', 'uncompiled', 'autoMount', 'drives', 'parms', 'sticky'];
/**
* convertMD()
@ -942,10 +942,12 @@ MarkOut.prototype.convertMDLinks = function(sBlock)
* Before we start replacing Markdown links, see if there are any Liquid-style replacements
* (in case this Markdown file is part of a Jekyll installation) and remove them.
*
* TODO: These replacements should use appropriate values from _config.yml; however, unless/until
* we start using Node again to host the public site, that's low priority.
* TODO: Any double-brace replacements should use appropriate values from _config.yml or the
* page's Front Matter; however, unless/until we start using Node again to host the public site,
* that's low priority.
*/
sBlock = sBlock.replace(/\{([\{%]).*?\1}/g, "");
sBlock = sBlock.replace(/([^\t])\{([\{%]).*?\2}/g, "$1");
sBlock = sBlock.replace(/(\{)([\{%])(.*?\2})/g, "<pre><code>$1$2$3</code></pre>");
var aMatch;
var re = /\[([^\[\]]*)]\((.*?)(?:\s*"(.*?)"\)|\))/g;
@ -1150,23 +1152,24 @@ MarkOut.prototype.convertMDImageLinks = function(sBlock, sIndent)
*/
MarkOut.prototype.convertMDMachineLinks = function(sBlock)
{
var aMatch, sReplacement;
var aMatch, sReplacement, machine;
var sMachineType, sMachineID, sMachineXMLFile, sMachineXSLFile, sMachineVersion, sMachineOptions, sMachineParms;
/*
* Before we start looking for Markdown-style machine links, see if there are any Liquid-style machines,
* (in case this Markdown file is part of a Jekyll installation) and convert them to Markdown-style links.
*/
var reIncludes = /\{%\s*include\s+machine\.html\s+id=(["'])(.*?)\1\s*%}/g;
var reIncludes = /(.){%\s*include\s+machine\.html\s+id=(["'])(.*?)\2\s*%}/g;
while ((aMatch = reIncludes.exec(sBlock))) {
if (aMatch[1] == '\t') continue;
sReplacement = "";
sMachineID = aMatch[2];
sMachineID = aMatch[3];
if (this.aMachineDefs[sMachineID]) {
var machine = this.aMachineDefs[sMachineID];
machine = this.aMachineDefs[sMachineID];
sMachineType = machine['type'] || "PCx86";
sMachineOptions = ((sMachineType.indexOf("-dbg") > 0 || machine['debugger'] == "true")? "debugger" : "");
if (machine['sticky']) sMachineOptions += (sMachineOptions? "," : "") + "sticky";
sMachineType = sMachineType.replace("-dbg", "");
sMachineXMLFile = machine['config'] || this.sMachineFile || "machine.xml";
sMachineXSLFile = machine['template'] || "";
@ -1175,11 +1178,14 @@ MarkOut.prototype.convertMDMachineLinks = function(sBlock)
sReplacement = machine['name'] || "Embedded PC";
sReplacement = "[" + sReplacement + "](" + sMachineXMLFile + ' "' + sMachineType + '!' + sMachineID + '!' + sMachineXSLFile + '!!' + sMachineOptions + '!' + sMachineParms + '")';
}
sBlock = sBlock.replace(aMatch[0], sReplacement);
sBlock = sBlock.replace(aMatch[0].substr(1), sReplacement);
reIncludes.lastIndex = 0; // reset lastIndex, since we just modified the string that reIncludes is iterating over
}
sBlock = sBlock.replace(/\{%\s*include\s+build\.html\s+id=(["'])(.*?)\1\s*%}/g, '<div class="buildpc" id=$1$2$1></div>');
/*
* Ditto for any Liquid-style machine build links.
*/
sBlock = sBlock.replace(/\{%\s*include\s+machine-build\.html\s+id=(["'])(.*?)\1\s*%}/g, '<div class="buildpc" id=$1$2$1></div>');
/*
* Start looking for Markdown-style machine links now...
@ -1204,6 +1210,7 @@ MarkOut.prototype.convertMDMachineLinks = function(sBlock)
sMachineParms = aMachineParms[4] || "";
var aMachineOptions = sMachineOptions.split(',');
var fDebugger = (aMachineOptions.indexOf("debugger") >= 0);
var fSticky = (aMachineOptions.indexOf("sticky") >= 0);
/*
* TODO: Consider validating the existence of this XML file and generating a more meaningful error if not found
@ -1247,10 +1254,48 @@ MarkOut.prototype.convertMDMachineLinks = function(sBlock)
'xsl': sMachineXSLFile,
'version': sMachineVersion,// eg, "1.10", "*" to select the current version, or "uncompiled"; "*" is the default
'debugger': fDebugger, // eg, true or false; false is the default
'sticky': fSticky, // eg, true or false; false is the default
'parms': sMachineParms}
);
}
/*
* Last but not least, see if there are any Liquid-style machine command links that need to be converted.
*/
reIncludes = /([ \t]*)\{%\s*include\s+machine-command\.html\s+(.*?)\s*%}/g;
var findParm = function(aParms, sParm) {
sParm += '=';
for (var i = 0; i < aParms.length; i++) {
if (aParms[i].indexOf(sParm) == 0) {
return aParms[i].slice(sParm.length+1, -1);
}
}
return "";
};
while ((aMatch = reIncludes.exec(sBlock))) {
if (aMatch[1] == '\t') continue;
var aParms = aMatch[2].match(/[a-z]+=(["']).*?\1/g);
if (!aParms) continue;
sReplacement = "";
sMachineID = findParm(aParms, 'machine');
var sControl = findParm(aParms, 'type') || 'button';
var sControlType = sControl == 'button'? ' type="button"' : '';
if (this.aMachineDefs[sMachineID]) {
sReplacement = '<' + sControl + sControlType + ' onclick="commandMachine(';
sReplacement += "'" + sMachineID + "',";
sReplacement += "'" + findParm(aParms, 'component') + "',";
sReplacement += "'" + findParm(aParms, 'command') + "',";
sReplacement += "'" + findParm(aParms, 'value') + "')";
sReplacement += '">' + (findParm(aParms, 'label') || 'Try It!') + '</' + sControl + '>';
} else {
sReplacement = "<!-- Unrecognized machine ID: " + sMachineID + " -->";
}
sBlock = sBlock.replace(aMatch[0], sReplacement);
reIncludes.lastIndex = 0; // reset lastIndex, since we just modified the string that reIncludes is iterating over
}
if (cMatches) {
sBlock = sBlock.replace(/^<p>([\s\S]*)<\/p>$/g, "$1");
}

View file

@ -0,0 +1,102 @@
/**
* @fileoverview Support for "sticky" machines
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a> (@jeffpar)
* @copyright © Jeff Parsons 2012-2017
*
* This file is part of PCjs, a computer emulation software project at <http://pcjs.org/>.
*
* PCjs 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 3
* of the License, or (at your option) any later version.
*
* PCjs 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 have received a copy of the GNU General Public License along with PCjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* <http://pcjs.org/modules/shared/lib/defines.js>.
*
* Some PCjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of PCjs
* for purposes of the GNU General Public License, and the author does not claim any copyright
* as to their contents.
*/
"use strict";
/**
* addStickyMachine(idMachine)
*
* @param {string} idMachine
*/
function addStickyMachine(idMachine)
{
var topMachine = -1;
var prevOnScroll = window.onscroll;
window.onscroll = function() {
/*
* TODO: Determine if/when we can cache the machine and machineSibling elements; we already
* know we can't cache them when addStickyMachine() is first called, because that currently
* happens before embed.js replaces the placeholder machine <div> with the real machine <div>.
*
* Placement of the addStickyMachine() call is irrelevant, because embed.js asynchronously
* reads all the XML files that define the machine *before* replacing the <div>.
*/
var machine = document.getElementById(idMachine);
if (machine) {
var machineSibling = machine.nextElementSibling;
if (machineSibling) {
if (topMachine < 0) {
topMachine = findTop(machine);
}
machine.className = machine.className.replace(/pcjs-machine-(floating|sticky) /g, '');
if (window.pageYOffset <= topMachine) {
machine.className = 'pcjs-machine-floating ' + machine.className;
if (machineSibling) machineSibling.style.paddingTop = 0;
} else {
machine.className = 'pcjs-machine-sticky ' + machine.className;
if (machineSibling) machineSibling.style.paddingTop = machine.offsetHeight + 'px';
}
if (prevOnScroll) prevOnScroll();
}
}
};
}
/**
* commandMachine(idMachine, typeComponent, sCommand, sValue)
*
* @param {string} idMachine
* @param {string} typeComponent
* @param {string} sCommand
* @param {string} [sValue]
*/
function commandMachine(idMachine, typeComponent, sCommand, sValue)
{
console.log("commandMachine('" + idMachine + "','" + typeComponent + "','" + sCommand + "','" + sValue + "')");
}
/**
* findTop(obj)
*
* @param {Object} obj
*/
function findTop(obj)
{
var curTop = 0;
if (typeof obj.offsetParent != 'undefined' && obj.offsetParent) {
while (obj.offsetParent) {
curTop += obj.offsetTop;
obj = obj.offsetParent;
}
curTop += obj.offsetTop;
}
else if (obj.y) {
curTop += obj.y;
}
return curTop;
}

View file

@ -228,7 +228,19 @@
.pcjs-copyright a {
text-decoration: none;
}
.pcjs-machine-floating {
position: relative;
z-index: auto;
background-color: transparent;
padding-right: 0;
}
.pcjs-machine-sticky {
position: fixed !important;
z-index: 1 !important;
background-color: #404040 !important;
padding-right: 16px !important;
top: 0 !important;
}
@media screen and (max-width: 800px) {
.pcjs-textarea {
width: 100% !important;