Skip to content

Commit

Permalink
Merge pull request #2051 from xodio/feat-records
Browse files Browse the repository at this point in the history
Implement record patches feature
  • Loading branch information
brusherru authored Oct 28, 2020
2 parents 7dd0af1 + f89e7f7 commit ca24195
Show file tree
Hide file tree
Showing 67 changed files with 4,159 additions and 230 deletions.
25 changes: 25 additions & 0 deletions packages/xod-arduino/platform/nodes/record.tpl.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
node {
meta {
struct Type {
{{#each inputs}}
{{#if (isConstantType type)}}static constexpr {{/if~}}
typeof_{{ pinKey }} field_{{ pinKey }}
{{~#if (isConstantType type)}} = constant_input_{{ pinKey }}{{/if}};
{{/each}}
};
}

void evaluate(Context ctx) {
Type record;

{{#each inputs}}
{{#unless (isConstantType type)}}
record.field_{{ pinKey }} = getValue<input_{{ pinKey }}>(ctx);
{{/unless}}
{{/each}}

{{#each outputs}}
emitValue<output_{{ pinKey }}>(ctx, record);
{{/each}}
}
}
31 changes: 31 additions & 0 deletions packages/xod-arduino/platform/nodes/unpackRecord.tpl.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
node {
// Define types for templatable custom outputs
meta {
{{#each inputs}}
{{#each ../outputs}}
{{#if isTemplatableCustomTypePin}}
typedef decltype(get_member_type(&typeof_{{ ../pinKey }}::field_{{ recordField }})) typeof_{{ pinKey }};
{{/if}}
{{/each}}
{{/each}}
}

// Define constant outputs
{{#each inputs}}
{{#each ../outputs}}
{{#if (isConstantType type)}}
static constexpr typeof_{{ pinKey }} constant_output_{{ pinKey }} = typeof_{{ ../pinKey }}::field_{{ recordField }};
{{/if}}
{{/each}}
{{/each}}
void evaluate(Context ctx) {
{{#each inputs}}
auto record = getValue<input_{{ pinKey }}>(ctx);
{{/each}}
{{#each outputs}}
{{#unless (isConstantType type)}}
emitValue<output_{{ pinKey }}>(ctx, record.field_{{ recordField }});
{{/unless}}
{{/each}}
}
}
2 changes: 2 additions & 0 deletions packages/xod-arduino/platform/runtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ template<typename T> struct remove_pointer<T* const> {typedef T type;};
template<typename T> struct remove_pointer<T* volatile> {typedef T type;};
template<typename T> struct remove_pointer<T* const volatile> {typedef T type;};

template <typename T, typename M> M get_member_type(M T:: *);

//----------------------------------------------------------------------------
// Forward declarations
//----------------------------------------------------------------------------
Expand Down
16 changes: 15 additions & 1 deletion packages/xod-arduino/src/templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ import memoryH from '../platform/memory.h';
import stlH from '../platform/stl.h';
import runtimeCpp from '../platform/runtime.cpp';

import recordImplementation from '../platform/nodes/record.tpl.cpp';
import unpackRecordImplementation from '../platform/nodes/unpackRecord.tpl.cpp';

// =============================================================================
//
// Utils and helpers
Expand Down Expand Up @@ -456,6 +459,13 @@ const templates = {
patchImpl: Handlebars.compile(patchTpl, renderingOptions),
legacyPatchImpl: Handlebars.compile(legacyPatchTpl, renderingOptions),
program: Handlebars.compile(programTpl, renderingOptions),
nodes: {
record: Handlebars.compile(recordImplementation, renderingOptions),
unpackRecord: Handlebars.compile(
unpackRecordImplementation,
renderingOptions
),
},
};

// =============================================================================
Expand All @@ -480,7 +490,11 @@ export const renderPatchPinTypes = def(
const generatedCodeRegEx = /^\s*{{\s*GENERATED_CODE\s*}}\s*$/gm;

export const renderImpl = def('renderImpl :: TPatch -> String', tPatch => {
const impl = R.prop('impl', tPatch);
const impl = R.cond([
[R.prop('isRecord'), templates.nodes.record],
[R.prop('isUnpackRecord'), templates.nodes.unpackRecord],
[R.T, R.prop('impl')],
])(tPatch);
const generatedCode = renderPatchContext(tPatch);
const patchPinTypes = renderPatchPinTypes(tPatch);

Expand Down
61 changes: 53 additions & 8 deletions packages/xod-arduino/src/transpiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,24 +118,70 @@ export const commentXodPragmas = def(
R.replace(/^\s*#\s*pragma\s+XOD\s/gm, '//#pragma XOD ')
);

const GENERATIVE_IMPLEMENTATION = '';
const getPatchImpl = def(
'getPatchImpl :: Patch -> String',
R.cond([
// exceptions
[XP.isRecordPatch, R.always(GENERATIVE_IMPLEMENTATION)],
[XP.isUnpackRecordPatch, R.always(GENERATIVE_IMPLEMENTATION)],
[
R.T,
patch =>
R.compose(
explodeMaybe(
`Implementation for ${XP.getPatchPath(patch)} not found`
),
XP.getImpl
)(patch),
],
])
);

const convertPatchToTPatch = def(
'convertPatchToTPatch :: Patch -> TPatch',
patch => {
const patchPath = XP.getPatchPath(patch);
const impl = explodeMaybe(
`Implementation for ${patchPath} not found`,
XP.getImpl(patch)
);
const impl = getPatchImpl(patch);

const isDirtyable = pin =>
XP.getPinType(pin) === XP.PIN_TYPE.PULSE ||
isDirtienessEnabled(impl, `${pin.direction}_${pin.label}`);

// Additional field for Patch outputs `recordField`
// used only in code generation for unpack record patch.
// It uses normalization of empty pin labels with opposite
// directions, so if the unpack record patch has few outputs
// with empty labels it will be normalized to `IN1`, `IN2` and so on.
// This labels will be used in record proprty accessors (`record.IN1`),
// while the outputs of unpack patch will be `OUT1` as usual.
//
// :: Map PinKey PinLabel
// where PinLabel is normalized record field
const recordFields = R.ifElse(
() => XP.isUnpackRecordPatch(patch),
R.compose(
R.fromPairs,
R.map(pin => [XP.getPinKey(pin), XP.getPinLabel(pin)]),
R.map(R.over(pinLabelLens, cppEscape)),
XP.normalizeEmptyPinLabelsOppositeDirection,
XP.listOutputPins
),
R.always({})
)(patch);
// :: PinKey -> Nullable PinLabel
const getRecordField = R.ifElse(
R.has(R.__, recordFields),
R.prop(R.__, recordFields),
R.always(null)
);

const outputs = R.compose(
R.map(
R.applySpec({
type: XP.getPinType,
pinKey: XP.getPinLabel,
recordField: R.compose(getRecordField, XP.getPinKey),
value: R.compose(XP.defaultValueOfType, XP.getPinType),
isDirtyable,
isDirtyOnBoot: R.compose(
Expand Down Expand Up @@ -166,6 +212,8 @@ const convertPatchToTPatch = def(
const isThisIsThat = {
isDefer: XP.isDeferNodeType(patchPath),
isConstant: XP.isConstantNodeType(patchPath),
isRecord: XP.isRecordPatch(patch),
isUnpackRecord: XP.isUnpackRecordPatch(patch),
usesTimeouts: areTimeoutsEnabled(impl),
usesSetImmediate: isSetImmediateEnabled(impl),
catchesErrors: doesCatchErrors(impl),
Expand Down Expand Up @@ -435,10 +483,7 @@ const getTNodeOutputDestinations = def(
: exceptionPinKeys.some(pk => inputPinKeys.includes(pk)); // if at least one input is whitelisted
},
getEvaluateOnPinSettings,
explodeMaybe(
`Implementation for ${XP.getPatchPath(patch)} not found`
),
XP.getImpl
getPatchImpl
)(patch);

return {
Expand Down
1 change: 1 addition & 0 deletions packages/xod-arduino/test/transpiler.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ describe('xod-arduino transpiler', () => {
specify('blink', () => testFixture('blink'));
specify('two-button-switch', () => testFixture('two-button-switch'));
specify('lcd-time', () => testFixture('lcd-time'));
specify('record-pack-unpack', () => testFixture('record-pack-unpack'));
specify('count-with-feedback-loops', () =>
testFixture('count-with-feedback-loops')
);
Expand Down
8 changes: 8 additions & 0 deletions packages/xod-client-browser/src/containers/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { HotKeys } from 'react-hotkeys';
import * as XP from 'xod-project';
import client from 'xod-client';
import { foldEither, notNil } from 'xod-func-tools';
import { LIVENESS } from 'xod-arduino';

import packageJson from '../../package.json';
import PopupInstallApp from '../components/PopupInstallApp';
Expand Down Expand Up @@ -275,6 +276,13 @@ class App extends client.App {
]),
submenu(items.deploy, [
onClick(items.showCodeForArduino, this.onShowCodeArduino),
...(process.env.IS_DEV
? [
onClick(items.showCodeWithDebug, () =>
this.onShowCodeArduino(LIVENESS.DEBUG)
),
]
: []),
onClick(items.uploadToArduino, this.onUpload),
onClick(items.runSimulation, this.onRunSimulation),
]),
Expand Down
6 changes: 6 additions & 0 deletions packages/xod-client-browser/webpack.config.dev.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const path = require('path');
/* eslint-disable import/no-extraneous-dependencies */
const webpack = require('webpack');
const merge = require('webpack-merge');
/* eslint-enable import/no-extraneous-dependencies */
const baseConfig = require('./webpack.config.js');
Expand All @@ -18,4 +19,9 @@ module.exports = merge.smart(baseConfig, {
contentBase: pkgpath('dist'),
compress: true,
},
plugins: [
new webpack.DefinePlugin({
'process.env.IS_DEV': true,
}),
],
});
9 changes: 8 additions & 1 deletion packages/xod-client-electron/src/view/containers/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ import {
proceedPackageUpgrade,
} from '../../arduinoDependencies/actions';
import { loadWorkspacePath } from '../../app/workspaceActions';
import { getPathToBundledWorkspace } from '../../app/utils';
import { getPathToBundledWorkspace, IS_DEV } from '../../app/utils';

import getLibraryNames from '../../arduinoDependencies/getLibraryNames';

Expand Down Expand Up @@ -677,6 +677,13 @@ class App extends client.App {
]),
submenu(items.deploy, [
onClick(items.showCodeForArduino, this.onShowCodeArduino),
...(IS_DEV
? [
onClick(items.showCodeWithDebug, () =>
this.onShowCodeArduino(LIVENESS.DEBUG)
),
]
: []),
onClick(items.uploadToArduino, this.onUploadToArduinoClicked),
onClick(items.runSimulation, this.onRunSimulation),
onClick(
Expand Down
4 changes: 2 additions & 2 deletions packages/xod-client/src/core/containers/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export default class App extends React.Component {
this.props.actions.fetchGrant(/* startup */ true);
}

onShowCodeArduino() {
onShowCodeArduino(liveness = LIVENESS.NONE) {
R.compose(
foldEither(
R.compose(
Expand All @@ -104,7 +104,7 @@ export default class App extends React.Component {
),
R.map(transpile),
this.transformProjectForTranspiler
)(LIVENESS.NONE);
)(liveness);
}

onRunSimulation() {
Expand Down
8 changes: 8 additions & 0 deletions packages/xod-client/src/hinting/validation.funcs.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ export const getConstructorMarkersErrorMap = getMarkerNodesErrorMap(
'validateConstructorPatch'
);

// :: Patch -> Map NodeId (Map ErrorType [Error])
export const getRecordMarkersErrorMap = getMarkerNodesErrorMap(
R.pipe(XP.getNodeType, R.equals(XP.RECORD_MARKER_PATH)),
XP.validateRecordPatch,
'validateRecordPatch'
);

// :: Patch -> Map NodeId (Map ErrorType [Error])
export const getBusesErrorMap = (patch, _project) =>
R.compose(
Expand Down Expand Up @@ -169,6 +176,7 @@ export const defaultValidateFunctions = {
getVariadicMarkersErrorMap,
getAbstractMarkersErrorMap,
getConstructorMarkersErrorMap,
getRecordMarkersErrorMap,
getBusesErrorMap,
],
pins: [validateBoundValues],
Expand Down
3 changes: 3 additions & 0 deletions packages/xod-client/src/utils/menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ const rawItems = {
showCodeForArduino: {
label: 'Show Code for Arduino',
},
showCodeWithDebug: {
label: 'Show Code with Debug',
},
uploadToArduino: {
label: 'Upload to Arduino...',
},
Expand Down
19 changes: 19 additions & 0 deletions packages/xod-project/built-in-patches.xodball
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,25 @@
"description": "Deprecates a patch which contains this node. Use it to archive outdated patch nodes. The projects which already uses the deprecated node will not break. Only a warning message will be shown. Use the marker node description to provide a deprecation reason and migration hint, it will be shown along the warning to end-users. For new projects, deprecated nodes are unlisted by default, so usage of such nodes by library users is discouraged.",
"path": "@/deprecated"
},
"@/record": {
"description": "Transforms the patch to a record type constructor.",
"path": "@/record"
},
"@/unpack-record": {
"description": "Node that marks a generated unpack patch for the record for further code generation and processing.",
"path": "@/unpack-record",
"nodes": {
"utilityMarker": {
"id": "utilityMarker",
"position": {
"x": 0,
"y": 0,
"units": "slots"
},
"type": "xod/patch-nodes/utility"
}
}
},
"@/from-bus": {
"description": "Defines an attachment point to a patch-level data bus with the name defined by this node label.",
"nodes": {
Expand Down
Loading

0 comments on commit ca24195

Please sign in to comment.