├── demo
├── node_test.js
├── sandbox
│ └── shaders.html
├── js
│ └── ParametricGeometries.js.download
└── demo.html
├── .gitignore
├── img
└── heron-source-torus.png
├── src
├── utils.ts
├── heron-normal-form.ts
├── code-builder.ts
├── type-parser.ts
├── heron-compiler.ts
├── heron-refs.ts
├── js-intrinsics.js
├── heron-name-analysis.ts
├── heron-defs.ts
├── heron-statement.ts
├── heron-to-html.ts
├── tests.ts
├── heron-to-js.ts
├── heron-to-glsl.ts
├── heron-package.ts
└── heron-to-text.ts
├── tsconfig.json
├── input
├── sandbox
│ ├── color.heron
│ ├── square-cubes.heron
│ ├── geometry-transform.heron
│ ├── simple_optimizer.py
│ ├── geometry-quaternion.heron
│ ├── voronoi-distances.heron
│ ├── experiment-with-interfaces.txt
│ ├── seascape.heron
│ ├── noise.heron
│ └── geometry-matrix4x4.heron
├── geometry-vector3.heron
├── test.heron
├── array.heron
├── intrinsics.heron
└── geometry-mesh.heron
├── .vscode
├── tasks.json
└── launch.json
├── package.json
├── LICENSE
├── tools
├── gen-spec.js
└── gen-visitor.js
├── source-browser
└── styles.css
├── comparison.md
├── index.html
├── history.md
└── assets
└── expected-types.txt
/demo/node_test.js:
--------------------------------------------------------------------------------
1 | require('./output.js');
2 | process.exit();
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/type-inference/.vscode/tasks.json
2 | node_modules/
3 |
--------------------------------------------------------------------------------
/img/heron-source-torus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cdiggins/heron-language/HEAD/img/heron-source-torus.png
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | export type LabelTypes = 'chooseFunc'|'funcType';
2 |
3 | export function trace(label: LabelTypes, message: string) {
4 | //console.log(message);
5 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": true,
3 | "compilerOptions": {
4 | "target": "ES5",
5 | "module": "commonjs",
6 | "sourceMap": true,
7 | "outDir": "build",
8 | "noUnusedLocals": true,
9 | "strict": false,
10 | //"noUnusedParameters": true,
11 | },
12 | "include": [
13 | "src/*.ts"
14 | ],
15 | "exclude": [
16 | "node_modules"
17 | ]
18 | }
--------------------------------------------------------------------------------
/input/sandbox/color.heron:
--------------------------------------------------------------------------------
1 | module color
2 | {
3 | function HUEtoRGB(hue) {
4 | var H = mod(hue,1.0);
5 | var R = abs(H * 6.0 - 3.0) - 1.0;
6 | var G = 2.0 - abs(H * 6.0 - 2.0);
7 | var B = 2.0 - abs(H * 6.0 - 4.0);
8 | return clamp(vec(R,G,B),0.0,1.0);
9 | }
10 |
11 | function HSLtoRGB(HSL) {
12 | var RGB = HUEtoRGB(HSL.x);
13 | var C = (1.0 - abs(2.0 * HSL.z - 1.0)) * HSL.y;
14 | return (RGB - 0.5) * C.vector + HSL.z.vector;
15 | }
16 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.1.0",
3 |
4 | // The command is tsc.
5 | "command": "tsc",
6 |
7 | // Show the output window only if unrecognized errors occur.
8 | "showOutput": "always",
9 |
10 | // Under windows use tsc.exe. This ensures we don't need a shell.
11 | "windows": {
12 | "command": "tsc"
13 | },
14 |
15 | // args is the program to compile.
16 | "args": ["--watch"],
17 |
18 | "isShellCommand": true,
19 |
20 | // use the standard tsc problem matcher to find compile problems
21 | // in the output.
22 | "problemMatcher": "$tsc"
23 | }
--------------------------------------------------------------------------------
/input/sandbox/square-cubes.heron:
--------------------------------------------------------------------------------
1 | // My implementation of some F# code found at:
2 | // https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/active-patterns
3 |
4 | module squarecubes:1.0
5 | {
6 | var err = 1.e-10
7 |
8 | function isInteger(x)
9 | = abs(x - x.round) < err;
10 |
11 | function isSquare(x)
12 | = x.float.sqrt.isInteger;
13 |
14 | function isCube(x)
15 | = x.float.pow(1/3).isInteger;
16 |
17 | function isSquareCube(x)
18 | = x.isSquare || x.isCube;
19 |
20 | var test
21 | = (1 .. 1000000).filter(isSquareCube);
22 | }
--------------------------------------------------------------------------------
/input/sandbox/geometry-transform.heron:
--------------------------------------------------------------------------------
1 | language heron:std:0.1;
2 |
3 | module heron:geometry.transform:0.1
4 | {
5 | function transform(pos, rot, scl)
6 | = { position=pos; rotation=rot; scale=scl; };
7 |
8 | function translation(pos)
9 | = transform(pos, quaternion_zero, ones);
10 |
11 | function rotation(rot)
12 | = transform(origin, rot, ones);
13 |
14 | function scaling(scl)
15 | = transform(origin, quaternion_zero, scl);
16 |
17 | function matrix(t)
18 | = translation_rotation_scaling_matrix(t.position, t.rotation, t.scale);
19 | }
20 |
--------------------------------------------------------------------------------
/input/sandbox/simple_optimizer.py:
--------------------------------------------------------------------------------
1 | // https://www.codementor.io/zhuojiadai/julia-vs-r-vs-python-simple-optimization-gnqi4njro
2 |
3 | import numpy as np
4 | from scipy.optimize import minimize
5 | from scipy.stats import norm
6 |
7 | # generate the data
8 | odr=[0.10,0.20,0.15,0.22,0.15,0.10,0.08,0.09,0.12]
9 | Q_t = norm.ppf(odr)
10 | maxQ_t = max(Q_t)
11 |
12 | # define a function that will return a return to optimize based on the input data
13 | def neglik_trunc_tn(Q_t):
14 | maxQ_t = max(Q_t)
15 | def neglik_trunc_fn(musigma):
16 | return -sum(norm.logpdf(Q_t, musigma[0], musigma[1])) + len(Q_t)*norm.logcdf(maxQ_t, musigma[0], musigma[1])
17 | return neglik_trunc_fn
18 |
19 | # the likelihood function to optimize
20 | neglik = neglik_trunc_tn(Q_t)
21 |
22 | # optimize!
23 | res = minimize(neglik, [np.mean(Q_t), np.std(Q_t)])
24 | res
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "heron-lang",
3 | "version": "0.3.0",
4 | "description": "A cross-platform programming language inspired by JavaScript that emphasizes simplicity, safety, and code reuse.",
5 | "main": "src/heron.js",
6 | "homepage": "https://github.com/cdiggins/heron-language",
7 | "bugs": "https://github.com/cdiggins/heron-language/issues",
8 | "license": "MIT",
9 | "author": {
10 | "name": "Christopher Diggins",
11 | "email": "cdiggins@gmail.com"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "https://github.com/cdiggins/heron-language.git"
16 | },
17 | "scripts": {
18 | "build": "tsc",
19 | "watch": "tsc --watch",
20 | "lint": "tslint --project ."
21 | },
22 | "dependencies": {
23 | "myna-parser": "^2.5.1"
24 | },
25 | "devDependencies": {
26 | "tslint": "^5.9.1"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/heron-normal-form.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Heron Normal Form (HNF)
3 | *
4 | * An intermediate compiler representation for optimization, transformation, code-generation,
5 | * analysis, and evaluation.
6 | *
7 | * Expression ::=
8 | * | Non-function (Bool, Int, Float, String, etc.)
9 | * | Function
10 | * | Function Set (Array of possible functions)
11 | * | Closure (Function + Closure record)
12 | * | Free variable (reference to entry in closure record)
13 | * | Parameter (bound variable)
14 | * | Function call (Function + Array of Expressions)
15 | * | Condition ( Expression ? Expression : Expression )
16 | *
17 | * Other intermediate forms to look at:
18 | * * https://en.wikipedia.org/wiki/A-normal_form.
19 | * * https://en.wikipedia.org/wiki/Static_single_assignment_form
20 | * * https://en.wikipedia.org/wiki/Continuation-passing_style
21 | */
22 |
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Christopher Diggins
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/tools/gen-spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const Myna = require("myna-parser");
4 | const g = require('../build/heron-parser').heronGrammar;
5 | const fs = require('fs');
6 |
7 | const m = Myna.Myna;
8 | const pkg = JSON.parse(fs.readFileSync('./package.json', "utf8"));
9 | const version = pkg.version;
10 | const schema = m.astSchemaToString('heron');
11 | const grammar = m.grammarToString('heron');
12 |
13 | const contents =
14 | [
15 | '# Heron Specification ' + version,
16 | '',
17 | '## AST Schema',
18 | '',
19 | 'This is the schema of the abstract syntax tree (AST) created when',
20 | 'when parsing Heron with the [Myna parser](https://github.com/cdiggins/myna-parser)',
21 | 'prior to any transformations',
22 | '',
23 | '```',
24 | schema,
25 | '```',
26 | '',
27 | '## PEG Grammar',
28 | '',
29 | 'This is the full grammar for Heron in PEG form. ',
30 | 'Alternatively you can view the [source code for the parser](https://github.com/cdiggins/heron-language/blob/master/src/heron-parser.ts)',
31 | '',
32 | '```',
33 | grammar,
34 | '```'
35 | ].join('\n');
36 |
37 | fs.writeFileSync('./spec.md', contents);
38 |
39 | process.exit();
40 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible Node.js debug attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 |
8 | {
9 | "type": "node",
10 | "request": "launch",
11 | "name": "Heron Test Output",
12 | "program": "${workspaceRoot}/demo/node_test.js",
13 | "cwd": "${workspaceRoot}",
14 | "outFiles": ["${workspaceRoot}/build/**/*.js"],
15 | "sourceMaps": true
16 | },
17 | {
18 | "type": "node",
19 | "request": "launch",
20 | "name": "Launch Program",
21 | "program": "${workspaceRoot}/build/tests.js",
22 | "cwd": "${workspaceRoot}",
23 | "outFiles": ["${workspaceRoot}/build/**/*.js"],
24 | "sourceMaps": true
25 | },
26 | {
27 | "type": "node",
28 | "request": "attach",
29 | "name": "Attach to Process",
30 | "port": 5858,
31 | "outFiles": [],
32 | "timeout": 30000,
33 | "sourceMaps": true
34 | }
35 | ]
36 | }
--------------------------------------------------------------------------------
/src/code-builder.ts:
--------------------------------------------------------------------------------
1 | //=====================================
2 | // Helper class for constructing pretty printerd code
3 | // this is passed as a "state" object to visitors
4 |
5 | function count(s: string, sub: string) {
6 | return s.split(sub).length - 1;
7 | }
8 |
9 | export class CodeBuilder
10 | {
11 | lines: string[] = [];
12 | indent: number = 0;
13 | get indentString() {
14 | let r = '';
15 | for (let i=0; i < this.indent; ++i)
16 | r += ' ';
17 | return r;
18 | }
19 | pushLine(s: string = '') {
20 | this.push(s + '\n');
21 | this.lines.push(this.indentString);
22 | }
23 | push(s: string) {
24 | let indentDelta = count(s, '{') - count(s, '}');
25 | indentDelta += count(s, '(') - count(s, ')');
26 | this.indent += indentDelta;
27 | if (indentDelta < 0) {
28 | if (this.lines.length > 0) {
29 | const lastLine = this.lines[this.lines.length-1].trim();
30 | if (lastLine.length === 0) {
31 | this.lines.pop();
32 | this.lines.push(this.indentString);
33 | }
34 | }
35 | }
36 | this.lines.push(s);
37 | }
38 | toString(): string {
39 | return this.lines.join('');
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/input/geometry-vector3.heron:
--------------------------------------------------------------------------------
1 | language heron:std:0.1;
2 |
3 | module heron:geometry.vector:0.1
4 | {
5 | // Several algorithms inspired by the following:
6 | // https://referencesource.microsoft.com/#System.Numerics/System/Numerics/Vector3
7 | // https://referencesource.microsoft.com/#System.Numerics/System/Numerics/Vector3_Intrinsics.cs
8 |
9 | // Variables
10 |
11 | var origin = vector(0, 0, 0);
12 | var ones = vector(1, 1, 1);
13 | var xaxis = vector(1, 0, 0);
14 | var yaxis = vector(0, 1, 0);
15 | var zaxis = vector(0, 0, 1);
16 |
17 | // Functions
18 |
19 | function vector(x: Float, y: Float, z: Float)
20 | = float3(x, y, z);
21 |
22 | function vector(x: Float)
23 | = vector(x, x, x);
24 |
25 | /*
26 | function vector(xs)
27 | = vector(xs[0], xs[1], xs[2]);
28 | */
29 |
30 | function array(v)
31 | = [v.x, v.y, v.z];
32 |
33 | function sumComponents(v)
34 | = v.x + v.y + v.z;
35 |
36 | function dot(a, b)
37 | = sumComponents(a * b);
38 |
39 | function length(v)
40 | = sqrt(v.length2);
41 |
42 | function length2(v)
43 | = v.dot(v);
44 |
45 | function distance(a, b)
46 | = (a - b).length;
47 |
48 | function distance2(a, b)
49 | = (a - b).length2;
50 |
51 | function normal(v)
52 | = v / v.length.vector;
53 |
54 | function cross(a, b)
55 | = vector(a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x);
56 |
57 | function reflect(v, n)
58 | = v - (n * dot(v, n) * 2.0);
59 |
60 | function lerp(a, b, x)
61 | = a * (1.0 - x) + b * x;
62 |
63 | function negate(v)
64 | = vector(-v.x, -v.y, -v.z);
65 | }
--------------------------------------------------------------------------------
/source-browser/styles.css:
--------------------------------------------------------------------------------
1 | .code {
2 | background-color: #202020;
3 | white-space: pre;
4 | font-family: 'Inconsolata', 'Courier New', monospace;
5 | font-size: 14;
6 | color: white;
7 | padding: 10px;
8 | }
9 | .string {
10 | color: rosybrown;
11 | }
12 | .number {
13 | color: #f099bb;
14 | }
15 | .bool {
16 | color: #f099bb;
17 | }
18 | .fullComment, .lineComment {
19 | color: royalblue;
20 | font-style: italic;
21 | }
22 | .varName {
23 | color: goldenrod;
24 | }
25 | /*
26 | .langVer, .moduleName, .urn {
27 | color: darkgreen;
28 | }
29 | */
30 | .keyword {
31 | color: #8aabfd;
32 | }
33 | .operator {
34 | color: #9497a2;
35 | }
36 | .funcName {
37 | color: white;
38 | }
39 | .funcParamName {
40 | color: #f2be3b;
41 | }
42 | .varNameDecl {
43 | color: #f2be3b;
44 | }
45 | .functionDef {
46 | position: relative;
47 | }
48 | .functionType {
49 | color: white;
50 | background-color: #333;
51 | opacity: 0.8;
52 | display: none;
53 | position: absolute;
54 | font-size: 0.8em;
55 | top: -1em;
56 | }
57 | .hideTypes .functionDef:hover > .functionType {
58 | display: inline-block;
59 | position: absolute;
60 | }
61 | .showTypes .functionType {
62 | display: inline-block;
63 | position: absolute;
64 | }
65 | .gitHubLink {
66 | position: fixed;
67 | left: 50%;
68 | top: 10px;
69 | transform: translateX(-50%);
70 | }
71 | .gitHubLink a {
72 | color: pink;
73 | }
74 | .button {
75 | position: fixed;
76 | right: 20;
77 | background-color: steelblue;
78 | border: none;
79 | color: white;
80 | text-align: center;
81 | text-decoration: none;
82 | padding: 15px;
83 | opacity: 0.8;
84 | }
85 | .button > a {
86 | color: black;
87 | }
--------------------------------------------------------------------------------
/demo/sandbox/shaders.html:
--------------------------------------------------------------------------------
1 |
18 |
19 |
27 |
28 |
29 |
46 |
47 |
--------------------------------------------------------------------------------
/src/type-parser.ts:
--------------------------------------------------------------------------------
1 | import { Type, typeConstant, typeVariable, polyType } from "./type-system";
2 | import { Myna as m } from "myna-parser"
3 |
4 | // Defines syntax parsers for type expression, the lambda calculus, and Cat
5 | function registerGrammars()
6 | {
7 | // A simple grammar for parsing type expressions
8 | class TypeGrammar
9 | {
10 | typeExprRec = m.delay(() => this.typeExpr);
11 | typeList = m.guardedSeq('(', m.ws, this.typeExprRec.ws.zeroOrMore, ')').ast;
12 | typeVar = m.guardedSeq("'", m.identifier).ast;
13 | typeConstant = m.identifier.or(m.digits).or("->").or("*").or("[]").ast;
14 | typeExpr = m.choice(this.typeList, this.typeVar, this.typeConstant).ast;
15 | }
16 | const typeGrammar = new TypeGrammar();
17 | m.registerGrammar('type', typeGrammar, typeGrammar.typeExpr);
18 |
19 | }
20 |
21 | registerGrammars();
22 |
23 | export const typeParser = m.parsers['type'];
24 |
25 | export function parseType(input:string) : Type|null {
26 | var ast = typeParser(input);
27 | if (ast.end != input.length)
28 | throw new Error("Only part of input was consumed");
29 | return astToType(ast);
30 | }
31 |
32 | function astToType(ast: any) : Type|null {
33 | if (!ast)
34 | return null;
35 | switch (ast.name)
36 | {
37 | case "typeVar":
38 | return typeVariable(ast.allText.substr(1));
39 | case "typeConstant":
40 | return typeConstant(ast.allText);
41 | case "typeList":
42 | return polyType(ast.children.map(astToType));
43 | case "typeExpr":
44 | if (ast.children.length != 1)
45 | throw new Error("Expected only one child of node, not " + ast.children.length);
46 | return astToType(ast.children[0]);
47 | default:
48 | throw new Error("Unrecognized type expression: " + ast.name);
49 | }
50 | }
51 |
52 |
--------------------------------------------------------------------------------
/tools/gen-visitor.js:
--------------------------------------------------------------------------------
1 | // Variable usage
2 | // - Declaration
3 | // - Usage
4 | // - function calls
5 |
6 | const m = require("myna-parser");
7 | const g = require('../build/heron-parser').heronGrammar;
8 | const grammarName = "heron";
9 |
10 | function createAstVisitorFunction(rule, lines) {
11 | lines.push(" visit_" + rule.name + "(ast, state) {");
12 | lines.push(" // " + rule.astRuleDefn());
13 | lines.push(" this.visitChildren(ast, state);");
14 | lines.push(" }")
15 | }
16 |
17 | function createCaseStatement(rule, lines) {
18 | lines.push(" case '" + rule.name + "': ");
19 | lines.push(" // " + rule.astRuleDefn());
20 | lines.push(" ast['property'] = somevalue;");
21 | lines.push(" break;")
22 | }
23 |
24 | function createAstVisitor() {
25 | var lines = [
26 | "class " + grammarName + "Visitor",
27 | "{",
28 | " visitNode(ast, state) {",
29 | " const fnName = 'visit_' + ast.name;",
30 | " if (fnName in this)",
31 | " this[fnName](ast, state);",
32 | " else",
33 | " this.visitChildren(ast, state);",
34 | " }",
35 | " visitChildren(ast, state) {",
36 | " for (let child of ast.children)",
37 | " this.visitNode(child, state);",
38 | " }"
39 | ];
40 | var rules = m.grammarAstRules(grammarName);
41 | for (var r of rules)
42 | createAstVisitorFunction(r, lines);
43 | lines.push("}");
44 |
45 | return lines.join("\n");
46 | }
47 |
48 | function createAstSwitch() {
49 | var lines = [
50 | "switch (ast.name) {"
51 | ];
52 | var rules = m.grammarAstRules(grammarName);
53 | for (var r of rules)
54 | createCaseStatement(r, lines);
55 | lines.push(' default:');
56 | lines.push("}");
57 |
58 | return lines.join("\n");
59 | }
60 |
61 | //const output = createAstVisitor();
62 | const output = createAstSwitch();
63 | console.log(output);
64 |
65 | process.exit();
--------------------------------------------------------------------------------
/comparison.md:
--------------------------------------------------------------------------------
1 | ## Heron Compared to TypeScript / JavaScript
2 |
3 | Heron most closely resembles a subset of the JavaScript language. It has a type system that is more restricted than TypeScript, but the type-inference system is more aggressive. For example Heron function parameter types are inferred based on usage in the function defintion, as opposed to resolving to `any`.
4 |
5 | The biggest standout difference is that Heron has no concept of classes or prototypes.
6 |
7 | Heron is an unordered list of various differences Heron has with TypeScript/JavaScript:
8 |
9 | * only primitive types, generic types (including array and function), and type variables
10 | * no object literals
11 | * no `this` keyword
12 | * functions can be called using dot notation on the first argument
13 | * functions can be ovoverloaded (two functions can have the same name if the inferred types are different)
14 | * operators can be overloaded
15 | * operators can be passed as functions
16 | * `var` statements are equivalent to `let` statements in TypeScript/JavaScript
17 | * no `const` statements
18 | * module level variables cannot be modified
19 | * variable types are inferred
20 | * parameter and return types of functions are inferred
21 | * variables have to always be initialized
22 | * variable binding expression allows variable declarations to be used as expressions
23 | * arrays are immutable
24 | * modifying arrays can only be done with `ArrayBuilder`
25 | * each `ArrayBuilder` modification creates a new array
26 | * only supports a `for..in` loop form which is the same as `for..of` loop in JavaScript
27 | * a built-in range operator `from..to` generates an array of contiguous values (exclusive upper bound)
28 | * arrays do not necessarily allocate memory, e.g. 0..100000000, has O(1) memory consumption
29 | * module names are URN's with the version number encoded in it
30 | * all files specify the version of the language
31 | * all definitions must be in a module
32 | * variables cannot be reassigned to objects of a different type
33 | * no `async` or `await` support
34 | * no operators spread support
35 | * no class or interface definitions
36 | * anonymous functions use a *fat arrow* syntax
37 | * Separation betwen integers (`Int`) and floating point numbers (`Float`)
38 | * Support for two, three, and four dimensional numerical types like in GLSL (`Float2`, `Float3`, `Float4`).
39 | * Semicolons are required as statement terminators.
40 | * No statement labels
41 | * No comma operator
42 | * No switch statement
43 |
--------------------------------------------------------------------------------
/input/sandbox/geometry-quaternion.heron:
--------------------------------------------------------------------------------
1 | language heron:std:0.1;
2 |
3 | // https://referencesource.microsoft.com/#System.Numerics/System/Numerics/Quaternion.cs
4 | // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/index.htm
5 | // https://github.com/mrdoob/three.js/blob/master/src/math/Vector3.js
6 |
7 | module heron:geometry.quaternion:0.1
8 | {
9 | function quaternion(v)
10 | = float4(v.x, v.y, v.z, v.w);
11 |
12 | function quaternion(x, y, z, w)
13 | = float4(x, y, z, w);
14 |
15 | function quaternion(x, y, z)
16 | = quaternion(x, y, z, 1);
17 |
18 | function quaternion(v, w)
19 | = quaternion(v.x, v.y, v.z, w);
20 |
21 | var quaternion_identity
22 | = quaternion(0, 0, 0, 1);
23 |
24 | var quaternion_zero
25 | = quaternion(0, 0, 0, 0);
26 |
27 | function length2(q)
28 | = q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w;
29 |
30 | function length(q)
31 | = q.length2.sqrt;
32 |
33 | // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm
34 | function quaternion(axis, angle)
35 | = quaternion(
36 | axis.x * sin(angle/2),
37 | axis.y * sin(angle/2),
38 | axis.z * sin(angle/2),
39 | cos(angle/2));
40 |
41 | function quaternion()
42 | = quaternion(0, 0, 0, 0);
43 |
44 | function angle(q)
45 | = 2 * acos(q.w);
46 |
47 | // http://www.euclideanspace.com/maths/geometry/rotations/conversions/eulerToQuaternion/index.htm
48 | function quaternion(heading, attitude, bank) {
49 | var c1 = cos(heading / 2);
50 | var c2 = cos(attitude / 2);
51 | var c3 = cos(bank / 2);
52 | var s1 = sin(heading / 2);
53 | var s2 = sin(attitude / 2);
54 | var s3 = sin(bank / 2);
55 | return quaternion(
56 | s1 * s2 * c3 + c1 * c2 * s3
57 | s1 * c2 * c3 + c1 * s2 * s3
58 | c1 * s2 * c3 - s1 * c2 * s3
59 | c1 * c2 * c3 - s1 * s2 * s3);
60 |
61 | // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/
62 | function axis(q)
63 | = vector(
64 | q.x / sqrt(1-q.w*q.w),
65 | q.y / sqrt(1-q.w*q.w),
66 | q.z / sqrt(1-q.w*q.w));
67 |
68 | function normal(q) =
69 | var invLen = 1.0f / q.length in
70 | quaternion(q.X * invLen, q.Y * invLen, q.Z * invLen, q.W * invLen);
71 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Heron
4 |
5 |
11 |
12 |
13 |
14 |
15 | Heron Language 3D Geometry Demo
16 |
17 |
18 |
19 |
20 | Heron is a new statically typed functional programming
21 | language with a JavaScript-like syntax that specializes in the processing of arrays of numerical data.
22 |
23 |
24 | This demo uses Heron to generate mesh data (vertices, faces, and colors)
25 | and uses Three.JS to display the resulting geometry.
26 | Browse the source code here:
27 |
33 |
34 |
35 | If you are interested in learning more, or collaborating please reach out to me,
36 | Christopher Diggins , via email
37 | LinkedIn , Twitter ,
38 | or GitHub .
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/heron-compiler.ts:
--------------------------------------------------------------------------------
1 | import {HeronAstNode } from "./heron-ast-rewrite";
2 | import { parseHeron, heronGrammar } from "./heron-parser";
3 | import { Package } from "./heron-package";
4 | import { computeFuncType, computeVarType } from "./heron-types";
5 |
6 | const g = heronGrammar;
7 |
8 | // Get some details about the language implementation environment
9 | declare var require: any;
10 | const fs = require('fs');
11 | const path = require('path');
12 |
13 | // TODO: use or throw out.
14 | //const nodePackage = JSON.parse(fs.readFileSync('package.json','utf8'));
15 | //const ver = nodePackage.version;
16 | //const flavor = 'std';
17 |
18 | const ext = '.heron';
19 |
20 | // Module resolution
21 | export const moduleFolder = path.join('.', 'input');
22 | export const outputFolder = path.join('.', 'output');
23 | export const intrinsicModules = ['intrinsics'];
24 | export const modules: HeronAstNode[] = [];
25 |
26 | //================================================================
27 | // Main functions
28 |
29 | export function createPackage(moduleNames: string[]): Package {
30 | const pkg = new Package();
31 |
32 | // Load the intrinsic (built-in) modules
33 | for (const name of intrinsicModules)
34 | addModuleToPackage(name, true, pkg);
35 |
36 | // Load the specified modules (any order)
37 | for (const name of moduleNames)
38 | addModuleToPackage(name, false, pkg);
39 |
40 | // The package is doing the heavy lifting
41 | pkg.processModules();
42 |
43 | // Compute types
44 | for (const v of pkg.allVarDefs) {
45 | computeVarType(v);
46 | }
47 |
48 | // Compute types
49 | for (const f of pkg.allFuncDefs) {
50 | computeFuncType(f);
51 | }
52 |
53 | return pkg;
54 | }
55 |
56 | export function addModuleToPackage(name: string, intrinsic: boolean, pkg: Package) {
57 | const modulePath = moduleNameToPath(name);
58 | const ast = parseFile(modulePath);
59 | if (ast)
60 | pkg.addFile(ast, intrinsic, modulePath);
61 | }
62 |
63 | export function moduleNameToPath(f: string): string {
64 | return path.join(moduleFolder, f + ext);
65 | }
66 |
67 | export function parseModule(moduleName: string): HeronAstNode|null {
68 | const modulePath = moduleNameToPath(moduleName);
69 | return parseFile(modulePath);
70 | }
71 |
72 | export function parseFile(f: string): HeronAstNode|null {
73 | try
74 | {
75 | const code = fs.readFileSync(f, 'utf-8');
76 | const ast = parseHeron(code, g.file);
77 | return ast;
78 | }
79 | catch (e)
80 | {
81 | console.log("An error occurred while parsing " + f);
82 | console.log(e.message);
83 | return null;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/heron-refs.ts:
--------------------------------------------------------------------------------
1 | // This module provides support for dealing with reference.
2 | // A reference is a name (identifier) that refers to a definition.
3 | // There can be more than one definitions (for examples when dealing with overloaded functions)
4 |
5 | import { Def, VarDef, ForLoopVarDef, TypeDef, TypeParamDef, FuncDef, FuncParamDef } from "./heron-defs";
6 | import { throwError, HeronAstNode } from "./heron-ast-rewrite";
7 | import { Scope } from "./heron-name-analysis";
8 |
9 | // A reference to one or more definitions.
10 | export class Ref
11 | {
12 | constructor(
13 | public readonly node: HeronAstNode,
14 | public readonly name: string,
15 | public readonly scope: Scope,
16 | public readonly defs: Def[])
17 | {
18 | node.ref = this;
19 | if (defs.length === 0)
20 | throwError(node, 'No definition found for ' + name);
21 | }
22 |
23 | toString(): string {
24 | return this.name + '_' + this.node['id'] + ':' + this.node.name + '[' + this.defs.join(', ') + ']';
25 | }
26 | }
27 |
28 | export class VarRef extends Ref {
29 | constructor(
30 | public readonly node: HeronAstNode,
31 | public readonly name: string,
32 | public readonly scope: Scope,
33 | public readonly def: VarDef)
34 | {
35 | super(node, name, scope, [def]);
36 | }
37 | }
38 |
39 | export class ForLoopVarRef extends Ref {
40 | constructor(
41 | public readonly node: HeronAstNode,
42 | public readonly name: string,
43 | public readonly scope: Scope,
44 | public readonly def: ForLoopVarDef)
45 | {
46 | super(node, name, scope, [def]);
47 | }
48 | }
49 |
50 | export class FuncParamRef extends Ref {
51 | constructor(
52 | public readonly node: HeronAstNode,
53 | public readonly name: string,
54 | public readonly scope: Scope,
55 | public readonly def: FuncParamDef)
56 | {
57 | super(node, name, scope, [def]);
58 | }
59 | }
60 |
61 | export class TypeRef extends Ref {
62 | constructor(
63 | public readonly node: HeronAstNode,
64 | public readonly name: string,
65 | public readonly scope: Scope,
66 | public readonly def: TypeDef)
67 | {
68 | super(node, name, scope, [def]);
69 | }
70 | }
71 |
72 | export class TypeParamRef extends Ref {
73 | constructor(
74 | public readonly node: HeronAstNode,
75 | public readonly name: string,
76 | public readonly scope: Scope,
77 | public readonly def: TypeParamDef)
78 | {
79 | super(node, name, scope, [def]);
80 | }
81 | }
82 |
83 | export class FuncRef extends Ref {
84 | constructor(
85 | public readonly node: HeronAstNode,
86 | public readonly name: string,
87 | public readonly scope: Scope,
88 | public readonly defs: FuncDef[])
89 | {
90 | super(node, name, scope, defs);
91 | }
92 | }
--------------------------------------------------------------------------------
/src/js-intrinsics.js:
--------------------------------------------------------------------------------
1 | function op_dot_dot(from, to) { const r=[]; for (let i=from; i < to; ++i) r.push(i); return r; }
2 | function int(x) { return Math.round(x); }
3 | function float(x) { return x; }
4 | function float2(u, v) { return ({ u: u, v: v }); }
5 | function float3(x, y, z) { return ({ x: x, y: y, z: z }); }
6 | function float4(x, y, z, w) { return ({ x: x, y: y, z: z, w: w }); }
7 | function u(v) { return v.u; }
8 | function v(v) { return v.v; }
9 | function x(v) { return v.x; }
10 | function y(v) { return v.y; }
11 | function z(v) { return v.z; }
12 | function w(v) { return v.w; }
13 | function xyz(v) { return float3(v.x, v.y, v.z); }
14 | function abs(x) { return Math.abs(x); }
15 | function acos(x) { return Math.acos(x); }
16 | function asin(x) { return Math.asin(x); }
17 | function atan(x) { return Math.atan(x); }
18 | function atan2(y, x) { return Math.atan2(y, x); }
19 | function ceil(x) { return Math.ceil(x); }
20 | function cos(x) { return Math.cos(x); }
21 | function exp(x) { return Math.exp(x); }
22 | function floor(x) { return Math.floor(x); }
23 | function log(x) { return Math.log(x); }
24 | function pow(x, y) { return Math.pow(x, y); }
25 | function round(x) { return Math.round(x); }
26 | function sin(x) { return Math.sin(x); }
27 | function sqrt(x) { return Math.sqrt(x); }
28 | function tan(x) { return Math.tan(x); }
29 | function clamp(x, min, max) { return x < min ? min : x > max ? max : x; };
30 | function sign(x) { return x > 0 ? 1 : x < 0 ? -1 : 0; };
31 | function op_add(x, y) { return x + y; };
32 | function op_sub(x, y) { return x - y; };
33 | function op_mul(x, y) { return x * y; };
34 | function op_div(x, y) { return x / y; };
35 | function op_mod(x, y) { return x % y; };
36 | function op_gt(x, y) { return x > y; };
37 | function op_gt_eq(x, y) { return x >= y; };
38 | function op_lt(x, y) { return x < y; };
39 | function op_lt_eq(x, y) { return x <= y; };
40 | function op_not_eq(x, y) { return x !== y; };
41 | function op_eq_eq(x, y) { return x === y; };
42 | function op_amp_amp(x, y) { return x && y; };
43 | function op_bar_bar(x, y) { return x || y; };
44 | function op_hat_hat(x, y) { return !!(x ^ y); };
45 | function op_not(x) { return !x; };
46 | function op_negate(x) { return -x; };
47 | function count(xs) { return xs.length; };
48 | function at(xs, i) { return xs[i]; };
49 | function array(count, at) { const r=[]; for (let i=0; i < count; ++i) r.push(at(i)); return r; }
50 | function mutable(xs) { return xs; }
51 | function immutable(xs) { return xs; }
52 | function push(xs, x) { return (xs.push(x), xs); };
53 | function pushMany(xs, ys) { return (xs.push(...ys), xs); };
54 | function set(xs, i, x) { return (xs[i] = x, xs); };
55 | function print(x) { return console.log(x); }
56 | function assert(condition) { if (!condition) throw new Error("assertion failed"); };
57 | function mesh(vertexBuffer, indexBuffer, uvBuffer, colorBuffer, normalBuffer) { return ({ vertexBuffer, indexBuffer, uvBuffer, colorBuffer, normalBuffer }); };
58 | function vertexBuffer(mesh) { return mesh.vertexBuffer; };
59 | function indexBuffer(mesh) { return mesh.indexBuffer; };
60 | function colorBuffer(mesh) { return mesh.colorBuffer; };
61 | function uvBuffer(mesh) { return mesh.uvBuffer; };
62 | function normalBuffer(mesh) { return mesh.normalBuffer; }
--------------------------------------------------------------------------------
/input/sandbox/voronoi-distances.heron:
--------------------------------------------------------------------------------
1 | // http://www.iquilezles.org/www/articles/voronoilines/voronoilines.htm
2 | // The MIT License
3 | // Copyright © 2013 Inigo Quilez
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
5 | // http://www.iquilezles.org/www/articles/voronoilines/voronoilines.htm
6 | //
7 |
8 | module voronoi_distances
9 | {
10 | function procedural_white_noise( p )
11 | {
12 | return fract(sin(vec2(dot(p,vec2(127.1,311.7)),dot(p,vec2(269.5,183.3))))*43758.5453);
13 | }
14 |
15 | function hash2( p )
16 | {
17 | return procedural_white_noise(p);
18 | }
19 |
20 | function voronoi( x )
21 | {
22 | n = floor(x);
23 | f = fract(x);
24 |
25 | //----------------------------------
26 | // first pass: regular voronoi
27 | //----------------------------------
28 | let mg = vec2;
29 | let mr = vec2;
30 |
31 | let md = 8.0;
32 | for( let j=-1; j<=1; j++ )
33 | for( let i=-1; i<=1; i++ )
34 | {
35 | let g = vec2(float(i),float(j));
36 | let o = hash2( n + g );
37 | let o = 0.5 + 0.5*sin( iGlobalTime + 6.2831*o );
38 | let r = g + o - f;
39 | let d = dot(r,r);
40 |
41 | if( d
0.00001 )
62 | md = min( md, dot( 0.5*(mr+r), normalize(r-mr) ) );
63 | }
64 |
65 | return vec3( md, mr );
66 | }
67 |
68 | function mainImage( fragCoord )
69 | {
70 | let p = fragCoord.xy/iResolution.xx;
71 |
72 | let c = voronoi( 8.0*p );
73 |
74 | // isolines
75 | let col = c.x*(0.5 + 0.5*sin(64.0*c.x))*vec3(1.0);
76 | // borders
77 | col = mix( vec3(1.0,0.6,0.0), col, smoothstep( 0.04, 0.07, c.x ) );
78 | // feature points
79 | let dd = length( c.yz );
80 | col = mix( vec3(1.0,0.6,0.1), col, smoothstep( 0.0, 0.12, dd) );
81 | col += vec3(1.0,0.6,0.1)*(1.0-smoothstep( 0.0, 0.04, dd));
82 |
83 | return vec4(col,1.0);
84 | }
85 | }
--------------------------------------------------------------------------------
/input/test.heron:
--------------------------------------------------------------------------------
1 | language heron:std:0.1;
2 |
3 | module heron:tests:0.1
4 | {
5 | import heron:std.array:0.1;
6 | import heron:geometry.mesh:0.1;
7 | import heron:geometry.vector:0.1;
8 |
9 | function main() {
10 | simpleArrayTest();
11 | simpleVectorTest();
12 | }
13 |
14 | function simpleArrayTest() {
15 | var xs = [1, 11, 3];
16 | print('Expect [1, 11, 3]');
17 | print(xs);
18 |
19 | print('Expect 1, 11, 3');
20 | for (var x in xs)
21 | print(x);
22 |
23 | print('Expect 1');
24 | print(xs[0]);
25 |
26 | print('Expect 3');
27 | print(xs.count);
28 |
29 | print('Expect 1');
30 | print(xs.first);
31 |
32 | print('Expect 3');
33 | print(xs.last);
34 |
35 | print('Expect 1');
36 | print(xs.min);
37 |
38 | print('Expect 11');
39 | print(xs.max);
40 |
41 | var ys = xs.mutable;
42 | ys[1] = 5;
43 | print('Expect 5');
44 | print(ys[1]);
45 |
46 | print('Expect 1, 3, 11');
47 | var zs = xs.sort;
48 | for (var z in zs)
49 | print(z);
50 |
51 | print('Expect 3');
52 | print(xs.median);
53 |
54 | print('Expect 15');
55 | print(xs.sum);
56 |
57 | print('Expect 5');
58 | print(xs.average);
59 | }
60 |
61 | function printVector(v) {
62 | var xs = [v.x, v.y, v.z];
63 | print(xs);
64 | }
65 |
66 | function simpleVectorTest() {
67 | var v = vector(4, 3, 0);
68 |
69 | print('Expect 5');
70 | print(v.length);
71 |
72 | print('Expect [4, 3, 0]');
73 | print(v);
74 |
75 | var v1 = vector(1, 0, 0);
76 | var v2 = vector(0, 1, 0);
77 | print('Expect [0,0,1]');
78 | print(v1.cross(v2));
79 |
80 | print('Expect [1,1,0]');
81 | var v3 = v1 + v2;
82 | print(v3);
83 |
84 | print('Expect [1,0,0]');
85 | var v4 = v3 - v2;
86 | print(v4);
87 |
88 | print('Expect [20,15,0]');
89 | var v5 = v * 5.0.vector;
90 | print(v5);
91 |
92 | print('Expect [1,1,0]');
93 | print(v1 + v2);
94 |
95 | print('Expect [0.8, 0.6, 0]');
96 | print(v.normal);
97 | }
98 |
99 | function colorModifier(g, amount: Float)
100 | = g.setVertexColors(g.uvBuffer.map(v => vector(
101 | sin(v.x * pi * 4f) / 2.0 + 0.5,
102 | sin(amount),
103 | cos(v.y * pi * 4f) / 2.0 + 0.5)));
104 |
105 | function pushModifer(g, amount: Float)
106 | = g.translate(g.normalBuffer.map(n => n * amount.vector));
107 |
108 | function scaleModifier(g, amount: Float)
109 | = g.scale(amount.vector);
110 |
111 | function modifiers()
112 | = [
113 | scaleModifier,
114 | pushModifer,
115 | colorModifier
116 | ];
117 |
118 | function primitives()
119 | = [
120 | sphere(),
121 | cylinder(),
122 | torus(),
123 | klein(),
124 | plane(),
125 | mobius(),
126 | tetrahedron,
127 | cube,
128 | octahedron,
129 | dodecahedron,
130 | icosahedron
131 | ];
132 | }
--------------------------------------------------------------------------------
/input/sandbox/experiment-with-interfaces.txt:
--------------------------------------------------------------------------------
1 | language heron:std:0.1;
2 |
3 | // An intrinsic is a function that has an implementation provided by the compiler.
4 |
5 | module heron:intrinsics:0.1
6 | {
7 | // These are core types
8 | type Float;
9 | type Float2;
10 | type Float3;
11 | type Float4;
12 | type Bool;
13 | type String;
14 | type Array;
15 |
16 | // These are interface (aka traits or protocols)
17 | interface INumerical;
18 | interface IOrderable;
19 | interface IComparable;
20 |
21 | var pi = 3.14159265358979323846;
22 | var e = 2.71828182845904523536;
23 |
24 | intrinsic float2(x: Float, y: Float): Float2;
25 | intrinsic x(v: Float2): Float;
26 | intrinsic y(v: Float2): Float;
27 |
28 | intrinsic float3(x: Float, y: Float, z: Float): Float3;
29 | intrinsic x(v: Float3): Float;
30 | intrinsic y(v: Float3): Float;
31 | intrinsic z(v: Float3): Float;
32 |
33 | intrinsic float4(x: Float, y: Float, z: Float, w: Float): Float4;
34 | intrinsic x(v: Float4): Float;
35 | intrinsic y(v: Float4): Float;
36 | intrinsic z(v: Float4): Float;
37 | intrinsic w(v: Float4): Float;
38 |
39 | interface INumerical {
40 | abs(x: T): T;
41 | acos(x: T): T;
42 | asin(x: T): T;
43 | atan(x: T): T;
44 | atan2(y: T, x: T): T;
45 | ceil(x: T): T;
46 | clamp(x: T, min: T, max: T): T;
47 | cos(x: T): T;
48 | exp(x: T): T;
49 | floor(x: T): T;
50 | log(x: T): T;
51 | max(x: T, y: T): T;
52 | min(x: T, y: T): T;
53 | pow(x: T, y: T): T;
54 | round(x: T): T;
55 | sin(x: T): T;
56 | sign(x: T): T;
57 | sqrt(x: T): T;
58 | tan(x: T): T;
59 |
60 | op+ (x: T, y: T): T;
61 | op- (x: T, y: T): T;
62 | op* (x: T, y: T): T;
63 | op/ (x: T, y: T): T;
64 | op% (x: T, y: T): T;
65 | }
66 |
67 | map(v: Vector2, f)
68 | = vector2(f(v.x), f(v.y));
69 |
70 | map(v: Vector3, f)
71 | = vector3(f(v.x), f(v.y), f(v.z));
72 |
73 | map(v: Vector4, f)
74 | = vector4(f(v.x), f(v.y), f(v.z), f(v.z));
75 |
76 | // Ignores the 'integer'
77 | abs, U: INumerical>(x: T)
78 | = x.map(abs);
79 |
80 | acos(x: T): T;
81 | asin(x: T): T;
82 | atan(x: T): T;
83 | atan2(y: T, x: T): T;
84 | ceil(x: T): T;
85 | clamp(x: T, min: T, max: T): T;
86 | cos(x: T): T;
87 | exp(x: T): T;
88 | floor(x: T): T;
89 | log(x: T): T;
90 | max(x: T, y: T): T;
91 | min(x: T, y: T): T;
92 | pow(x: T, y: T): T;
93 | round(x: T): T;
94 | sin(x: T): T;
95 | sign(x: T): T;
96 | sqrt(x: T): T;
97 | tan(x: T): T;
98 |
99 | op+ (x: T, y: T): T;
100 | op- (x: T, y: T): T;
101 | op* (x: T, y: T): T;
102 | op/ (x: T, y: T): T;
103 | op% (x: T, y: T): T;
104 |
105 | interface IMappable {
106 | map(x: T, f: (U) -> U)): T;
107 | }
108 |
109 | interface IOrderable {
110 | op> (x: T, y: T): Bool;
111 | op>= (x: T, y: T): Bool;
112 | op< (x: T, y: T): Bool;
113 | op<= (x: T, y: T): Bool;
114 | }
115 |
116 | interface IEquatable {
117 | op!= (x: T, y: T): Bool;
118 | op== (x: T, y: T): Bool;
119 | }
120 |
121 | // Special operators that have identifier names
122 | op_at(x: Array, i: Int): Any;
123 | op_range(from: Int, to: Int): Array;
124 | op_not(x: Bool): Bool;
125 | op_negate(x: T): T;
126 |
127 | }
--------------------------------------------------------------------------------
/src/heron-name-analysis.ts:
--------------------------------------------------------------------------------
1 | import { HeronAstNode } from "./heron-ast-rewrite";
2 | import { Def } from "./heron-defs";
3 | import { Ref } from "./heron-refs";
4 | import { Package } from "./heron-package";
5 |
6 | /**
7 | * Scope used for the purpose of name analysis creating ref/def chains.
8 | * A scope contains unique name declarations. Scopes are arranaged in a tree.
9 | */
10 | export class Scope
11 | {
12 | id: number = 0;
13 | refs: Ref[] = [];
14 | defs: Def[] = [];
15 | children: Scope[] = [];
16 | parent: Scope|null = null;
17 |
18 | constructor(public readonly node: HeronAstNode) {
19 | if (node)
20 | node['scope'] = this;
21 | }
22 |
23 | // We can find multiple defs at the same level (e.g. functions)
24 | findDefs(name: string): Def[] {
25 | let r = [];
26 | for (var d of this.defs)
27 | if (d.name === name)
28 | r.push(d);
29 | if (r.length > 0)
30 | return r;
31 | return this.parent
32 | ? this.parent.findDefs(name)
33 | : [];
34 | }
35 |
36 | allDefs(r: Def[] = []): Def[] {
37 | r.push(...this.defs);
38 | this.children.forEach(c => c.allDefs(r));
39 | return r;
40 | }
41 |
42 | allRefs(r: Ref[] = []): Ref[] {
43 | r.push(...this.refs);
44 | this.children.forEach(c => c.allRefs(r));
45 | return r;
46 | }
47 |
48 | allScopes(r: Scope[] = []): Scope[] {
49 | r.push(this);
50 | this.children.forEach(c => c.allScopes(r));
51 | return r;
52 | }
53 |
54 | toString(): string {
55 | if (!this.node)
56 | return "__global__";
57 | return nodeId(this.node);
58 | }
59 | }
60 |
61 | export function nodeId(node: HeronAstNode): string {
62 | return node ? node.name + '_' + node['id'] : '';
63 | }
64 |
65 | export const scopeType = ['funcDef', 'instrinsicDef', 'module', 'varExpr', 'compoundStatement'];
66 |
67 |
68 | // Used for visiting nodes in the Heron node looking for name defintions, usages, and scopes.
69 | export class NameAnalyzer
70 | {
71 | // Visitor helper functions
72 | visitNode(node: HeronAstNode, state: Package) {
73 | if (node.def)
74 | state.addDef(node.def);
75 | const fnName = 'visit_' + node.name;
76 | if (fnName in this)
77 | ((this as any)[fnName])(node, state);
78 | else
79 | this.visitChildren(node, state);
80 | }
81 | visitChildren(node: HeronAstNode, state: Package) {
82 | for (let child of node.children)
83 | this.visitNode(child, state);
84 | }
85 |
86 | // Particular node visitors
87 | visit_compoundStatement(node: HeronAstNode, state: Package) {
88 | state.pushScope(node);
89 | this.visitChildren(node, state);
90 | state.popScope();
91 | }
92 | visit_forLoop(node: HeronAstNode, state: Package) {
93 | state.pushScope(node);
94 | this.visitChildren(node, state);
95 | state.popScope();
96 | }
97 | visit_funcDef(node: HeronAstNode, state: Package) {
98 | state.pushScope(node);
99 | this.visitChildren(node, state);
100 | state.popScope();
101 | }
102 | visit_intrinsicDef(node: HeronAstNode, state: Package) {
103 | state.pushScope(node);
104 | this.visitChildren(node, state);
105 | state.popScope();
106 | }
107 | visit_typeName(node: HeronAstNode, state: Package) {
108 | state.addRef(node.allText, node);
109 | }
110 | visit_lambdaBody(node: HeronAstNode, state: Package) {
111 | state.pushScope(node);
112 | this.visitChildren(node, state);
113 | state.popScope();
114 | }
115 | visit_lambdaExpr(node: HeronAstNode, state: Package) {
116 | state.pushScope(node);
117 | this.visitChildren(node, state);
118 | state.popScope();
119 | }
120 | visit_varExpr(node: HeronAstNode, state: Package) {
121 | state.pushScope(node);
122 | this.visitChildren(node, state);
123 | state.popScope();
124 | }
125 | visit_varName(node: HeronAstNode, state: Package) {
126 | state.addRef(node.allText, node);
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/history.md:
--------------------------------------------------------------------------------
1 |
2 | # History of Heron
3 |
4 | ### Why throw away a good name?
5 |
6 | This is technically the third incarnation of the programming language Heron, but only a handful of people will remember the older versions. The last time I announced a programming language named Heron was in 2010. Previous incarnations were intended as general purpose languages whereas Heron is now much more focused in scope and purpose. However, much of the original philosophy and intention is present in the language so I've decided to stick to the name.
7 |
8 | The first large public announcement of the original Heron language was in 2006 https://developers.slashdot.org/story/04/12/08/1944233/introducing-the-heron-programming-language. That implementation was a Heron to C++ translator. It's primary contribution was a more advanced meta-programming system for C++ and a more regular syntax. At that time the another systems language was being developed by Walter Bright called D, which had similar design goals.
9 |
10 | Heron underwent several modifications and extensions and was revived in 2010 http://www.artima.com/weblogs/viewpost.jsp?thread=284558 with many new features. Achilleas Margaritas described the differences as "this new Heron seems to be a very different beast. It seems to be an enterprise-friendly application language like Java and C#, rather than a system/application one like C and C++.". This reflected my new found interest in C#, and the idea of code and diagrams being closely related.
11 |
12 | After finishing a working implementation of the new version of Heron, I realized that I had created a general purpose programming language that had no tool support, no community, and no libraries. Convincing any professional software developer to use Heron as opposed to an entrenched general purpose languages (C++, Java, C#, Scala, etc.) was going to be effectively impossible. I decided at that point to put Heron on ice and focus on other things.
13 |
14 | After spending several years working with 3D and animation software, including designing a visual programming language for 3D processing (Max Creation Graph), I've realized that there is a real need for an easy to use yet efficient programming language, which can target multiple platforms. Therefore I decided to reincarnate Heron as a programming language that specializes in efficient array and numerical processing, but that has the look and feel of JavaScript.
15 |
16 | I've found it to be a very pleasant experience writing libraries in Heron for array, numerical, 2D, and 3D processing. I hope you enjoy it as well, and I'd appreciate your feedback on how I can make it better!
17 |
18 | ## Postscript: May 7th 2023
19 |
20 | It has been five years since I have contributed to Heron. After releasing a v0.1 version of Heron that had
21 | a very powerful type-inference system and a syntax which I found agreeable, I realized that the next steps of
22 | getting people to use the language would be virtually insurmountable.
23 |
24 | How do you convince people to use a new language with extremely limited tooling, no libraries, and
25 | the only advantage is a nicer syntax with less type annotations? People already had enough languages
26 | to choose from, and type-safety with simpler syntax wasn't a strong enough motivator.
27 |
28 | In the last few years I moved back to primarily C# development in the realm of real-time 3D graphics.
29 | I developed numerous libraries and software using a primarily functional style of programming.
30 | Pure functional programming in C# works surprisingly well for real-time 3D graphics processing, it
31 | can produce very robust and relatively efficient code.
32 |
33 | However there were still limitations: the C# compiler still does not do a particularly good job of optimizing
34 | functional code, C# cannot be run efficiently in the browser, and writing numerical code in a pure functional
35 | style requires a ton of boilerplate.
36 |
37 | This inspired me to develop a language that would allow me to write my algorithms and data-structures in a C#
38 | style language while targetting multiple languages and platforms, with less boilerplate, and that could
39 | be easily optimized.
40 |
41 | This [new programming language is called Plato](https://github.com/cdiggins/plato) and it borrows many of the ideas
42 | and design goals of Heron. It is a more ambitious project with more features and is being built with the aim of
43 | bootstrapping itself.
44 |
--------------------------------------------------------------------------------
/assets/expected-types.txt:
--------------------------------------------------------------------------------
1 | adjacentDifferences(xs) :: (Func (Array T0) (Array T0))
2 | all(xs, p) :: (Func (Array T0) (Func T0 Bool) Bool)
3 | any(xs, p) :: (Func (Array T0) (Func T0 Bool) Bool)
4 | array(v) :: (Func Float3 (Array Float))
5 | array(v) :: (Func Float3 (Array Float))
6 | average(xs) :: (Func (Array Float) Float)
7 | cartesianProduct(xs, ys, f) :: (Func (Array T0) (Array T1) (Func T0 T1 T2) (Array T2))
8 | concat(xs, ys) :: (Func (Array T0) (Array T0) (Array T0))
9 | cross(a, b) :: (Func Float3 Float3 Float3)
10 | cut(xs, from, n) :: (Func (Array T0) Int Int (Array T0))
11 | cylinder() :: (Func Mesh)
12 | cylinder(segments) :: (Func Int Mesh)
13 | cylinderPoint(u, v) :: (Func Int Float Float3)
14 | distance(a, b) :: (Func Float3 Float3 Float)
15 | distance2(a, b) :: (Func Float3 Float3 Float)
16 | dot(a, b) :: (Func Float3 Float3 Float)
17 | drop(xs, n) :: (Func (Array T0) Int (Array T0))
18 | empty(xs) :: (Func (Array T0) Bool)
19 | eq(xs, ys) :: (Func (Array T0) (Array T1) Bool)
20 | faceCount(mesh) :: (Func Mesh Int)
21 | filter(xs, p) :: (Func (Array T0) (Func T0 Bool) (Array T0))
22 | first(xs) :: (Func (Array T0) T0)
23 | flatMap(xs, f) :: (Func (Array T0) (Func T0 (Array T1)) (Array T1))
24 | flatten(xs) :: (Func (Array (Array T0)) (Array T0))
25 | float3(xs) :: (Func (Array Float) Float3)
26 | gen(cnt, f) :: (Func Int (Func Int T0) (Array T0))
27 | geometryTest() :: (Func (Array (Func Float Float Float Mesh)))
28 | inRange(xs, n) :: (Func (Array T0) Int Bool)
29 | indices(xs) :: (Func (Array T0) (Array Int))
30 | last(xs, n) :: (Func (Array T0) Int (Array T0))
31 | last(xs) :: (Func (Array T0) T0)
32 | length(v) :: (Func Float3 Float)
33 | length2(v) :: (Func Float3 Float)
34 | lerp(a, b, x) :: (Func Float Float Float Float)
35 | longer(xs, ys) :: (Func (Array T0) (Array T0) (Array T0))
36 | main() :: (Func (Array (Func Float Float Float Mesh)))
37 | map(xs, f) :: (Func (Array T0) (Func T0 T1) (Array T1))
38 | mapIndex(xs, f) :: (Func (Array T0) (Func Int T1) (Array T1))
39 | mapWithIndex(xs, f) :: (Func (Array T0) (Func T0 Int T1) (Array T1))
40 | max(x, y) :: (Func T0 T0 T0)
41 | max(xs) :: (Func (Array T0) T0)
42 | median(xs) :: (Func (Array Int) Int)
43 | meshFromUV(f, uCount, vCount, uStart, vStart, uLength, vLength) :: (Func (Func Float Float Float3) Int Int Float Float Float Float Mesh)
44 | meshFromUV(f, segments) :: (Func (Func Float Float Float3) Int Mesh)
45 | min(xs) :: (Func (Array T0) T0)
46 | min(x, y) :: (Func T0 T0 T0)
47 | negate(v) :: (Func Float3 Float3)
48 | normal(v) :: (Func Float3 Float)
49 | op%(a, b) :: (Func Float3 Float3 Float3)
50 | op%(a, b) :: (Func Float2 Float2 Float2)
51 | op*(a, b) :: (Func Float2 Float2 Float2)
52 | op*(a, b) :: (Func Float3 Float3 Float3)
53 | op+(a, b) :: (Func Float2 Float2 Float2)
54 | op+(a, b) :: (Func Float3 Float3 Float3)
55 | op-(a, b) :: (Func Float2 Float2 Float2)
56 | op-(a, b) :: (Func Float3 Float3 Float3)
57 | op..(from, upto) :: (Func Int Int (Array Int))
58 | op/(a, b) :: (Func Float2 Float2 Float2)
59 | op/(a, b) :: (Func Float3 Float3 Float3)
60 | op[](xs, i) :: (Func (Array T0) Int T0)
61 | partition(a, lo, hi) :: (Func (Array T0) Int Int Int)
62 | prefixScan(xs, op) :: (Func (Array T0) (Func T0 T0 T0) (Array T0))
63 | product(xs) :: (Func (Array Float) Float)
64 | pushMany(xs, ys) :: (Func (ArrayBuilder T0) (Array T0) (ArrayBuilder T0))
65 | qsort(a, lo, hi) :: (Func (Array T0) Int Int (Array T0))
66 | quadStripToMeshIndices(vertices, rows, connectRows, connectCols) :: (Func (Array T0) Int Bool Bool (Array Int))
67 | reduce(xs, acc, f) :: (Func (Array T0) T1 (Func T1 T0 T1) T1)
68 | reflect(v, n) :: (Func Float3 Float3 Float)
69 | reify(xs) :: (Func (Array T0) (Array T0))
70 | repeat(x, n) :: (Func T0 Int (Array T0))
71 | rescale(v, from, length) :: (Func T0 T0 T0 T0)
72 | reverse(xs, n) :: (Func (Array T0) T1 (Array T0))
73 | scale(m, amount) :: (Func Mesh Float3 Mesh)
74 | selectByIndex(xs, indices) :: (Func (Array T0) (Array Int) (Array T0))
75 | setVertices(m, points) :: (Func Mesh (Array Float3) Mesh)
76 | shorter(xs, ys) :: (Func (Array T0) (Array T0) (Array T0))
77 | simpleArrayTest() :: (Func T0)
78 | skip(xs, n) :: (Func (Array T0) Int (Array T0))
79 | slice(xs, from, to) :: (Func (Array T0) Int Int (Array T0))
80 | slices(xs, n) :: (Func (Array T0) Int (Array (Array T0)))
81 | sort(xs) :: (Func (Array T0) (Array T0))
82 | sphere() :: (Func Mesh)
83 | sphere(segments) :: (Func Int Mesh)
84 | spherePoint(u, v) :: (Func Float Float Float3)
85 | splice(xs, from, ys) :: (Func (Array T0) Int (Array T0) (Array T0))
86 | stride(xs, from, n) :: (Func (Array T0) Int Int (Array T0))
87 | stride(xs, n) :: (Func (Array T0) Int (Array T0))
88 | strides(xs, n) :: (Func (Array T0) Int (Array (Array T0)))
89 | sum(xs) :: (Func (Array Float) Float)
90 | sumComponents(v) :: (Func Float3 Float)
91 | swapElements(xs, i, j) :: (Func (ArrayBuilder T0) Int Int (ArrayBuilder T0))
92 | tail(xs) :: (Func (Array T0) (Array T0))
93 | take(xs, n) :: (Func (Array T0) Int (Array T0))
94 | take(xs, i, n) :: (Func (Array T0) Int Int (Array T0))
95 | testSphere(offX, offY, offZ) :: (Func Float Float Float Mesh)
96 | toVertexBuffer(xs) :: (Func (Array Float3) (Array Float))
97 | torus(r1, r2, segments) :: (Func Float Float Int Mesh)
98 | torus() :: (Func Mesh)
99 | torusPoint(u, v, r1, r2) :: (Func Int Int Float Float Float3)
100 | transform(m, f) :: (Func Mesh (Func Float3 Float3) Mesh)
101 | translate(m, amount) :: (Func Mesh Float3 Mesh)
102 | unit(x) :: (Func T0 (Array T0))
103 | vector(x, y, z) :: (Func Float Float Float Float3)
104 | vector(uv) :: (Func Float2 Float3)
105 | vector(xs) :: (Func (Array Float) Float3)
106 | vector(x) :: (Func Float Float3)
107 | vertex(mesh, i) :: (Func Mesh Int Float3)
108 | vertexCount(mesh) :: (Func Mesh Int)
109 | vertices(mesh) :: (Func Mesh (Array Float3))
110 | zip(xs, ys, f) :: (Func (Array T0) (Array T1) (Func T0 T1 T2) (Array T2))
111 |
--------------------------------------------------------------------------------
/input/array.heron:
--------------------------------------------------------------------------------
1 | language heron:std:0.1;
2 |
3 | module heron:std.array:0.1
4 | {
5 | function unit(x)
6 | = [x];
7 |
8 | function map(xs, f)
9 | = array(xs.count, i => f(xs[i]));
10 |
11 | function mapWithIndex(xs, f)
12 | = array(xs.count, i => f(xs[i], i));
13 |
14 | function mapIndex(xs, f)
15 | = array(xs.count, f);
16 |
17 | function min(x, y)
18 | = x <= y ? x : y;
19 |
20 | function max(x, y)
21 | = x >= y ? x : y;
22 |
23 | function shorter(xs, ys)
24 | = xs.count <= ys.count ? xs : ys;
25 |
26 | function longer(xs, ys)
27 | = xs.count >= ys.count ? xs : ys;
28 |
29 | function empty(xs)
30 | = xs.count == 0;
31 |
32 | function selectByIndex(xs, indices)
33 | = indices.map(i => xs.at(i));
34 |
35 | function indices(xs)
36 | = 0 .. xs.count;
37 |
38 | function zip(xs, ys, f)
39 | = xs.count <= ys.count
40 | ? xs.mapWithIndex((x, i) => f(x, ys[i]))
41 | : ys.mapWithIndex((y, i) => f(xs[i], y));
42 |
43 | function all(xs, p)
44 | = xs.reduce(true, (prev, x) => prev && p(x));
45 |
46 | function any(xs, p)
47 | = xs.reduce(false, (prev, x) => prev || p(x));
48 |
49 | //function count(xs, p)
50 | // = xs.reduce(0, (prev, x) => p(x) ? prev + 1 : prev);
51 |
52 | function eq(xs, ys)
53 | = xs.count == ys.count;
54 |
55 | function filter(xs, p) {
56 | var ys = xs.mutable;
57 | var i = 0;
58 | for (var x in xs)
59 | if (p(x))
60 | ys[i++] = x;
61 | return ys.immutable.take(i);
62 | }
63 |
64 | function repeat(x, n)
65 | = (0 .. n).map(i => x);
66 |
67 | function prefixScan(xs, op) {
68 | if (xs.empty) return xs;
69 | var ys = xs[0].repeat(xs.count).mutable;
70 | for (var i in 1 .. ys.count)
71 | ys[i] = op(xs[i], ys[i-1]);
72 | return ys.immutable;
73 | }
74 |
75 | function adjacentDifferences(xs)
76 | = xs.indices.map(i => i > 0 ? xs[i] - xs[i-1] : xs[i]);
77 |
78 | function slice(xs, from, to)
79 | = (from .. to).map(i => xs.at(i));
80 |
81 | function stride(xs, n)
82 | = (0 .. xs.count / n).map(i => xs[i * n]);
83 |
84 | function stride(xs, from, n)
85 | = (0 .. xs.count / n).map(i => xs[from + i * n]);
86 |
87 | function strides(xs, n)
88 | = (0 .. n).map(i => xs.stride(i, n));
89 |
90 | function slices(xs, n)
91 | = (0 .. n).map(i => xs.slice(i * n, (i+1) * n));
92 |
93 | function take(xs, n)
94 | = xs.slice(0, n);
95 |
96 | function take(xs, i, n)
97 | = xs.skip(i).take(n);
98 |
99 | function skip(xs, n)
100 | = xs.slice(n, xs.count - n);
101 |
102 | function drop(xs, n)
103 | = xs.take(xs.count - n);
104 |
105 | function last(xs, n)
106 | = xs.skip(xs.count-n);
107 |
108 | function reverse(xs, n)
109 | = xs.indices.map(i => xs[xs.count-1-i]);
110 |
111 | function gen(cnt, f)
112 | = (0 .. cnt).map(f);
113 |
114 | function concat(xs, ys)
115 | = gen(xs.count + ys.count, i => i < xs.count ? xs[i] : ys[i - xs.count]);
116 |
117 | function cut(xs, from, n)
118 | = gen(xs.count - n, i => i < from ? xs[i] : xs[i + n]);
119 |
120 | function splice(xs, from, ys)
121 | = xs.take(from).concat(ys).concat(xs.skip(from));
122 |
123 | function sum(xs)
124 | = xs.reduce(0.0, op+);
125 |
126 | function product(xs)
127 | = xs.reduce(1.0, op*);
128 |
129 | function average(xs)
130 | = xs.sum / xs.count.float;
131 |
132 | function min(xs)
133 | = xs.reduce(xs[0], min);
134 |
135 | function max(xs)
136 | = xs.reduce(xs[0], max);
137 |
138 | function swapElements(xs, i, j) {
139 | var tmp = xs[i];
140 | xs[i] = xs[j];
141 | xs[j] = tmp;
142 | return xs;
143 | }
144 |
145 | function partition(a, lo, hi) {
146 | var p = a[lo];
147 | var i = lo - 1;
148 | var j = hi + 1;
149 | while (true) {
150 | do { i++; } while (a[i] < p);
151 | do { j--; } while (a[j] > p);
152 | if (i >= j) return j;
153 | swapElements(a, i, j);
154 | }
155 | }
156 |
157 | function qsort(a, lo, hi) {
158 | if (lo < hi) {
159 | var p = partition(a, lo, hi);
160 | qsort(a, lo, p);
161 | qsort(a, p+1, hi);
162 | }
163 | return a;
164 | }
165 |
166 | function sort(xs)
167 | = xs.mutable.qsort(0, xs.count-1).immutable;
168 |
169 | function median(xs) {
170 | var ys = xs.sort;
171 | return (ys.count - 1) % 2 == 0
172 | ? ys[(ys.count - 1) / 2]
173 | : ys[(ys.count - 2) / 2] + ys[ys.count / 2] / 2;
174 | }
175 |
176 | function inRange(xs, n)
177 | = n >= 0 && n < xs.count;
178 |
179 | function last(xs)
180 | = xs[xs.count - 1];
181 |
182 | function first(xs)
183 | = xs[0];
184 |
185 | function tail(xs)
186 | = xs.skip(1);
187 |
188 | function reduce(xs, acc, f) {
189 | for (var x in xs)
190 | acc = f(acc, x);
191 | return acc;
192 | }
193 |
194 | function flatten(xs)
195 | = xs.reduce([], concat);
196 |
197 | function flatMap(xs, f)
198 | = xs.map(f).flatten;
199 |
200 | // TODO: rewrite using flatMap, but that is slow for now. I will need to write an optimizer
201 | function cartesianProduct(xs, ys, f) {
202 | var r = [].mutable;
203 | for (var x in xs)
204 | for (var y in ys)
205 | r.push(f(x, y));
206 | return r.immutable;
207 | }
208 |
209 | function setAll(xs, x)
210 | = xs.map(_ => x);
211 | }
--------------------------------------------------------------------------------
/input/intrinsics.heron:
--------------------------------------------------------------------------------
1 | language heron:std:0.1;
2 |
3 | // Describes the core functionality provided by the run-time.
4 | module heron:intrinsics:0.1
5 | {
6 | // Built-in types
7 | type Float;
8 | type Float2;
9 | type Float3;
10 | type Float4;
11 | type Int;
12 | type Bool;
13 | type Array;
14 | type ArrayBuilder;
15 | type Func;
16 | type Any;
17 | type Mesh;
18 |
19 | // Intrinsic (built-in) functions.
20 | intrinsic float(x: Int): Float;
21 |
22 | intrinsic float2(x: Float, y: Float): Float2;
23 | intrinsic u(v: Float2): Float;
24 | intrinsic v(v: Float2): Float;
25 |
26 | intrinsic float3(x: Float, y: Float, z: Float): Float3;
27 | intrinsic x(v: Float3): Float;
28 | intrinsic y(v: Float3): Float;
29 | intrinsic z(v: Float3): Float;
30 |
31 | intrinsic float4(v: Float3, w: Float): Float4;
32 | intrinsic xyz(v: Float4): Float3;
33 | intrinsic w(v: Float4): Float;
34 |
35 | // Todo: eventually provide Float2, Float3, Float4 versions of these.
36 | // This is because a lot of hardware supports these at the lowest-level.
37 | // Implementing them in a high-level language is fine as a fall-back.
38 | intrinsic abs(x: Float): Float;
39 | intrinsic acos(x: Float): Float;
40 | intrinsic asin(x: Float): Float;
41 | intrinsic atan(x: Float): Float;
42 | intrinsic atan2(y: Float, x: Float): Float;
43 | intrinsic ceil(x: Float): Float;
44 | intrinsic clamp(x: Float, min: Float, max: Float): Float;
45 | intrinsic cos(x: Float): Float;
46 | intrinsic exp(x: Float): Float;
47 | intrinsic floor(x: Float): Float;
48 | intrinsic log(x: Float): Float;
49 | intrinsic pow(x: Float, y: Float): Float;
50 | intrinsic round(x: Float): Float;
51 | intrinsic sin(x: Float): Float;
52 | intrinsic sign(x: Float): Float;
53 | intrinsic sqrt(x: Float): Float;
54 | intrinsic tan(x: Float): Float;
55 |
56 | // Binary operators
57 | intrinsic op+ (x: T, y: T): T;
58 | intrinsic op- (x: T, y: T): T;
59 | intrinsic op* (x: T, y: T): T;
60 | intrinsic op/ (x: T, y: T): T;
61 | intrinsic op% (x: T, y: T): T;
62 |
63 | // Comparison operators
64 | intrinsic op> (x: T, y: T): Bool;
65 | intrinsic op>= (x: T, y: T): Bool;
66 | intrinsic op< (x: T, y: T): Bool;
67 | intrinsic op<= (x: T, y: T): Bool;
68 |
69 | // Specialized binary operators
70 | intrinsic op+ (x: Int, y: Int): Int;
71 | intrinsic op- (x: Int, y: Int): Int;
72 | intrinsic op* (x: Int, y: Int): Int;
73 | intrinsic op/ (x: Int, y: Int): Int;
74 | intrinsic op% (x: Int, y: Int): Int;
75 |
76 | intrinsic op+ (x: Float, y: Float): Float;
77 | intrinsic op- (x: Float, y: Float): Float;
78 | intrinsic op* (x: Float, y: Float): Float;
79 | intrinsic op/ (x: Float, y: Float): Float;
80 | intrinsic op% (x: Float, y: Float): Float;
81 |
82 | function op+ (a: Float2, b: Float2): Float2 = float2(a.x + b.x, a.y + b.y);
83 | function op- (a: Float2, b: Float2): Float2 = float2(a.x - b.x, a.y - b.y);
84 | function op* (a: Float2, b: Float2): Float2 = float2(a.x * b.x, a.y * b.y);
85 | function op/ (a: Float2, b: Float2): Float2 = float2(a.x / b.x, a.y / b.y);
86 | function op% (a: Float2, b: Float2): Float2 = float2(a.x % b.x, a.y % b.y);
87 |
88 | function op+ (a: Float3, b: Float3): Float3 = float3(a.x + b.x, a.y + b.y, a.z + b.z);
89 | function op- (a: Float3, b: Float3): Float3 = float3(a.x - b.x, a.y - b.y, a.z - b.z);
90 | function op* (a: Float3, b: Float3): Float3 = float3(a.x * b.x, a.y * b.y, a.z * b.z);
91 | function op/ (a: Float3, b: Float3): Float3 = float3(a.x / b.x, a.y / b.y, a.z / b.z);
92 | function op% (a: Float3, b: Float3): Float3 = float3(a.x % b.x, a.y % b.y, a.z % b.z);
93 |
94 | // Equality operators
95 | intrinsic op!= (x: T, y: T): Bool;
96 | intrinsic op== (x: T, y: T): Bool;
97 |
98 | // Boolean operators
99 | intrinsic op&& (x: Bool, y: Bool): Bool;
100 | intrinsic op|| (x: Bool, y: Bool): Bool;
101 | intrinsic op^^ (x: Bool, y: Bool): Bool;
102 |
103 | // Non-binary operators. These have identifier names.
104 | intrinsic op_not(x: Bool): Bool;
105 | intrinsic op_negate(x: Float): Float;
106 |
107 | // Array functions
108 | intrinsic array(n: Int, f: Func): Array;
109 | intrinsic count(xs: Array): Int;
110 | intrinsic at(xs: Array, i: Int): T;
111 | intrinsic op..(from: Int, to: Int): Array;
112 |
113 | // Array builder functions
114 | intrinsic mutable(xs: Array): ArrayBuilder;
115 | intrinsic push(xs: ArrayBuilder, x: T): ArrayBuilder;
116 | intrinsic pushMany(xs: ArrayBuilder, ys: Array): ArrayBuilder;
117 | intrinsic set(xs: ArrayBuilder, i: Int, x: T): ArrayBuilder;
118 | intrinsic immutable(xs: ArrayBuilder): Array;
119 |
120 | // Some helpers (todo: move to a separate library)
121 | //function array(v: Float2) = [v.x, v.y];
122 | function array(v: Float3) = [v.x, v.y, v.z];
123 | //function array(v: Float4) = [v.x, v.y, v.z, v.w];
124 |
125 | //function float2(xs) = float2(xs[0], xs[1]);
126 | function float3(xs) = float3(xs[0], xs[1], xs[2]);
127 | //function float4(xs) = float4(xs[0], xs[1], xs[2], xs[3]);
128 |
129 | // Force an evaluation of an array into memory. Used for debugging, or optimization.
130 | function reify(xs: Array): Array
131 | = xs.mutable.immutable;
132 |
133 | // Indexing operator
134 | function op[] (xs: Array, i: Int): T
135 | = at(xs, i);
136 |
137 | // A couple of useful constants
138 | var pi = 3.14159265358979323846;
139 | var e = 2.71828182845904523536;
140 |
141 | // Some thing we need for now.
142 | intrinsic print(x);
143 | intrinsic assert(x: Bool);
144 |
145 | // Geometry stuff
146 | intrinsic mesh(vertexBuffer: Array, indexBuffer: Array, uvBuffer: Array, colorBuffer: Array, normalBuffer: Array): Mesh;
147 | intrinsic vertexBuffer(mesh: Mesh): Array;
148 | intrinsic indexBuffer(mesh: Mesh): Array;
149 | intrinsic uvBuffer(mesh: Mesh): Array;
150 | intrinsic colorBuffer(mesh: Mesh): Array;
151 | intrinsic normalBuffer(mesh: Mesh): Array;
152 | }
153 |
--------------------------------------------------------------------------------
/input/sandbox/seascape.heron:
--------------------------------------------------------------------------------
1 | module heron.demos.glsl.seascape
2 | {
3 | requires('urn:heron:library:heron-lang:standard:1.0.0:heron.core');
4 | requires('urn:heron:library:heron-lang:standard:1.0.0:heron.glsl');
5 |
6 | // Any library using the library, would have to provide
7 | // these values before it could be used
8 | function initialize( iResolution, iGlobalTime)
9 | {
10 | // do nothing
11 | }
12 |
13 | var NUM_STEPS = 8;
14 | var PI = 3.141592;
15 | var EPSILON = 1e-3;
16 |
17 | // T
18 | var EPSILON_NRM = (0.1 / iResolution.x);
19 |
20 | // sea
21 | var ITER_GEOMETRY = 3;
22 | var ITER_FRAGMENT = 5;
23 | var SEA_HEIGHT = 0.6;
24 | var SEA_CHOPPY = 4.0;
25 | var SEA_SPEED = 0.8;
26 | var SEA_FREQ = 0.16;
27 | var SEA_BASE = vec3(0.1,0.19,0.22);
28 | var SEA_WATER_COLOR = vec3(0.8,0.9,0.6);
29 | var SEA_TIME = 1.0 + iGlobalTime * SEA_SPEED;
30 | var octave_m = mat2(1.6,1.2,-1.2,1.6);
31 |
32 | // math
33 | function fromEuler(ang) {
34 | var a1 = vec2(sin(ang.x),cos(ang.x));
35 | var a2 = vec2(sin(ang.y),cos(ang.y));
36 | var a3 = vec2(sin(ang.z),cos(ang.z));
37 | return mat3(
38 | vec3(a1.y*a3.y+a1.x*a2.x*a3.x,a1.y*a2.x*a3.x+a3.y*a1.x,-a2.y*a3.x),
39 | vec3(-a2.y*a1.x,a1.y*a2.y,a2.x),
40 | vec3(a3.y*a1.x*a2.x+a1.y*a3.x,a1.x*a3.x-a1.y*a3.y*a2.x,a2.y*a3.y));
41 | }
42 |
43 | function hash( p ) {
44 | var h = dot(p,vec2(127.1,311.7));
45 | return fract(sin(h)*43758.5453123);
46 | }
47 |
48 | function noise( p ) {
49 | var i = floor( p );
50 | var f = fract( p );
51 | var u = f*f*(3.0-2.0*f);
52 | return -1.0+2.0*mix( mix( hash( i + vec2(0.0,0.0) ),
53 | hash( i + vec2(1.0,0.0) ), u.x),
54 | mix( hash( i + vec2(0.0,1.0) ),
55 | hash( i + vec2(1.0,1.0) ), u.x), u.y);
56 | }
57 |
58 | // lighting
59 | function diffuse(n, l, p) {
60 | return pow(dot(n,l) * 0.4 + 0.6,p);
61 | }
62 |
63 | function specular(n, l, e, s) {
64 | var nrm = (s + 8.0) / (PI * 8.0);
65 | return pow(max(dot(reflect(e,n),l),0.0),s) * nrm;
66 | }
67 |
68 | // sky
69 | function getSkyColor( e ) {
70 | var y = max(e.y,0.0);
71 | return vec3(pow(1.0-y,2.0), 1.0-y, 0.6+(1.0-y)*0.4);
72 | }
73 |
74 | // sea
75 | function sea_octave( uv, choppy) {
76 | uv += noise(uv);
77 | var wv = 1.0-abs(sin(uv));
78 | var swv = abs(cos(uv));
79 | wv = mix(wv,swv,wv);
80 | return pow(1.0-pow(wv.x * wv.y,0.65), choppy);
81 | }
82 |
83 | function map(p) {
84 | var freq = SEA_FREQ;
85 | var amp = SEA_HEIGHT;
86 | var choppy = SEA_CHOPPY;
87 | var uv = p.xz; uv.x *= 0.75;
88 | var d = 0.0;
89 | var h = 0.0;
90 | for (var i in 0 .. ITER_GEOMETRY) {
91 | d = sea_octave((uv+SEA_TIME)*freq,choppy);
92 | d += sea_octave((uv-SEA_TIME)*freq,choppy);
93 | h += d * amp;
94 | uv *= octave_m; freq *= 1.9; amp *= 0.22;
95 | choppy = mix(choppy,1.0,0.2);
96 | }
97 | return p.y - h;
98 | }
99 |
100 | function map_detailed(p) {
101 | var freq = SEA_FREQ;
102 | var amp = SEA_HEIGHT;
103 | var choppy = SEA_CHOPPY;
104 | var uv = p.xz; uv.x *= 0.75;
105 | var d = 0.0;
106 | var h = 0.0;
107 | for (var i in 0 .. ITER_FRAGMENT) {
108 | d = sea_octave((uv+SEA_TIME)*freq,choppy);
109 | d += sea_octave((uv-SEA_TIME)*freq,choppy);
110 | h += d * amp;
111 | uv *= octave_m; freq *= 1.9; amp *= 0.22;
112 | choppy = mix(choppy,1.0,0.2);
113 | }
114 | return p.y - h;
115 | }
116 |
117 | function getSeaColor(p, n, l, eye, dist) {
118 | var fresnel = clamp(1.0 - dot(n,-eye), 0.0, 1.0);
119 | fresnel = pow(fresnel,3.0) * 0.65;
120 | var reflected = getSkyColor(reflect(eye,n));
121 | var refracted = SEA_BASE + diffuse(n,l,80.0) * SEA_WATER_COLOR * 0.12;
122 | var color = mix(refracted,reflected,fresnel);
123 | var atten = max(1.0 - dot(dist,dist) * 0.001, 0.0);
124 | color += SEA_WATER_COLOR * (p.y - SEA_HEIGHT) * 0.18 * atten;
125 | color += vec3(specular(n,l,eye,60.0));
126 | return color;
127 | }
128 |
129 | // tracing
130 | function getNormal(p, eps) {
131 | var n = vec3();
132 | n.y = map_detailed(p);
133 | n.x = map_detailed(vec3(p.x+eps,p.y,p.z)) - n.y;
134 | n.z = map_detailed(vec3(p.x,p.y,p.z+eps)) - n.y;
135 | n.y = eps;
136 | return normalize(n);
137 | }
138 |
139 | function heightMapTracing(ori, dir) {
140 | var tm = 0.0;
141 | var tx = 1000.0;
142 | var hx = map(ori + dir * tx);
143 | if(hx > 0.0) return p;
144 | var hm = map(ori + dir * tm);
145 | var tmid = 0.0;
146 | for (var i in 0 .. NUM_STEPS) {
147 | tmid = mix(tm,tx, hm/(hm-hx));
148 | p = ori + dir * tmid;
149 | var hmid = map(p);
150 | if (hmid < 0.0) {
151 | tx = tmid;
152 | hx = hmid;
153 | } else {
154 | tm = tmid;
155 | hm = hmid;
156 | }
157 | }
158 | return p;
159 | }
160 |
161 | function mainImage( fragCoord ) {
162 | var uv = fragCoord.xy / iResolution.xy;
163 | uv = uv * 2.0 - 1.0;
164 | uv.x *= iResolution.x / iResolution.y;
165 | var time = iGlobalTime * 0.3 + iMouse.x*0.01;
166 |
167 | // ray
168 | var ang = vec3(sin(time*3.0)*0.1,sin(time)*0.2+0.3,time);
169 | var ori = vec3(0.0,3.5,time*5.0);
170 | var dir = normalize(vec3(uv.xy,-2.0)); dir.z += length(uv) * 0.15;
171 | dir = normalize(dir) * fromEuler(ang);
172 |
173 | // tracing
174 | var p = heightMapTracing(ori,dir);
175 | var dist = p - ori;
176 | var n = getNormal(p, dot(dist,dist) * EPSILON_NRM);
177 | var light = normalize(vec3(0.0,1.0,0.8));
178 |
179 | // color
180 | var color = mix(
181 | getSkyColor(dir),
182 | getSeaColor(p,n,light,dir,dist),
183 | pow(smoothstep(0.0,-0.05,dir.y),0.3));
184 |
185 | // post
186 | return vec4(pow(color,vec3(0.75)), 1.0);
187 | }
188 | }
--------------------------------------------------------------------------------
/src/heron-defs.ts:
--------------------------------------------------------------------------------
1 | // This module provides support for dealing with definitions.
2 | // A definition could be a function definition, parameter definition, variable definition, type definition.
3 |
4 | import { validateNode, throwError, HeronAstNode } from "./heron-ast-rewrite";
5 | import { HeronType } from "./heron-types";
6 |
7 | // This is a definition of a name. It could be a function, variable, type
8 | export class Def {
9 | constructor(
10 | public readonly node: HeronAstNode,
11 | public readonly name: string,
12 | )
13 | { node.def = this; }
14 |
15 | // Added as a post-process step
16 | type?: HeronType;
17 |
18 | toString() {
19 | return this.name + '_' + (this.constructor as any)['name'] + this.node['id'];
20 | }
21 | }
22 |
23 | export function typeNodeToStr(node?: HeronAstNode|null): string {
24 | if (!node || !node.allText) return "any";
25 | return node.allText;
26 | }
27 |
28 | // Represents the definition of a function
29 | export class FuncDef extends Def
30 | {
31 | constructor(
32 | public readonly node: HeronAstNode,
33 | public readonly name: string,
34 | public readonly retTypeNode: HeronAstNode,
35 | public readonly params: FuncParamDef[],
36 | public readonly genericParams: TypeParamDef[],
37 | public readonly body: HeronAstNode,
38 | )
39 | { super(node, name); }
40 |
41 | toString(): string {
42 | return this.name + "(" + this.params.join(", ") + ")"; // + " : " + typeNodeToStr(this.retTypeNode);
43 | }
44 |
45 | get isIntrinsic(): boolean {
46 | return !this.body;
47 | }
48 | }
49 |
50 | // Represent a parameter to a function or a lambda expression
51 | export class FuncParamDef extends Def
52 | {
53 | constructor(
54 | public readonly node: HeronAstNode,
55 | public readonly name: string,
56 | public readonly typeNode?: HeronAstNode,
57 | )
58 | { super(node, name); }
59 |
60 | toString(): string {
61 | return this.name;// + (this.typeNode && this.typeNode.allText ? " : " + typeNodeToStr(this.typeNode) : '');
62 | }
63 | }
64 |
65 | // Represents the definition of a variable.
66 | export class VarDef extends Def
67 | {
68 | constructor(
69 | public readonly node: HeronAstNode,
70 | public readonly name: string,
71 | public readonly exprNode: HeronAstNode,
72 | )
73 | { super(node, name); }
74 | }
75 |
76 | // Represents the definition of a variable used in a for loop
77 | // The type is not known, until the type of the expression is figured out.
78 | // Unlike a VarDef the expression must be an array.
79 | export class ForLoopVarDef extends Def
80 | {
81 | constructor(
82 | public readonly node: HeronAstNode,
83 | public readonly name: string,
84 | public readonly exprNode: HeronAstNode,
85 | )
86 | { super(node, name); }
87 | }
88 |
89 | // Represents the definition of a type
90 | export class TypeDef extends Def
91 | {
92 | constructor(
93 | public readonly node: HeronAstNode,
94 | public readonly name: string,
95 | )
96 | { super(node, name); }
97 | }
98 |
99 | // Represents the definition of a generic type parameter
100 | export class TypeParamDef extends Def
101 | {
102 | constructor(
103 | public readonly node: HeronAstNode,
104 | public readonly name: string,
105 | public readonly constraint: HeronAstNode
106 | )
107 | { super(node, name); }
108 | }
109 |
110 | //==========================================================================================
111 | // Exported functions
112 |
113 | export function createDef(node: HeronAstNode): Def|null {
114 | // NOTE: typeParamDef and funcParamDef are created by the funcDef
115 | switch (node.name) {
116 | case "funcDef":
117 | case "intrinsicDef":
118 | return createFuncDef(node);
119 | case "typeDef":
120 | return createTypeDef(node);
121 | case "forLoop":
122 | return createForLoopVarDef(node);
123 | case "varDecl":
124 | return createVarDef(node);
125 | case "lambdaArg":
126 | case "lambdaArgNoType":
127 | return createFuncParamDef(node);
128 | }
129 | return null;
130 | }
131 |
132 | //==========================================================================================
133 |
134 | export function createTypeParam(node: HeronAstNode): TypeParamDef {
135 | validateNode(node, 'genericParam');
136 | let name = node.children[0].allText;
137 | let constraint = node.children.length > 1 ? node.children[1] : null;
138 | return new TypeParamDef(node, name, constraint);
139 | }
140 |
141 | export function createFuncDef(node: HeronAstNode): FuncDef {
142 | validateNode(node, 'funcDef', 'intrinsicDef');
143 | let sig = validateNode(node.children[0], 'funcSig');
144 | let name = sig.children[0].allText;
145 | let genericParamsNodes = validateNode(sig.children[1], 'genericParams');
146 | let genericParams = genericParamsNodes.children.map(createTypeParam);
147 | let params = validateNode(sig.children[2], 'funcParams').children.map(createFuncParamDef);
148 | let retType = (sig.children.length > 2) ? sig.children[3] : null;
149 | let body = node.children.length > 1 ? node.children[1] : null;
150 | return new FuncDef(node, name, retType, params, genericParams, body);
151 | }
152 |
153 | export function createFuncParamDef(node: HeronAstNode): FuncParamDef {
154 | validateNode(node, 'funcParam', 'lambdaArg', 'lambdaArgNoType');
155 | let name = node.children[0].allText;
156 | let type = (node.children.length > 1) ? node.children[1] : null;
157 | return new FuncParamDef(node, name, type);
158 | }
159 |
160 | export function createVarDef(node: HeronAstNode): VarDef {
161 | validateNode(node, 'varDecl');
162 | let name = validateNode(node.children[0], 'varNameDecl');
163 | return new VarDef(node, name.allText, node.children[1]);
164 | }
165 |
166 | export function createForLoopVarDef(node: HeronAstNode): ForLoopVarDef {
167 | validateNode(node, 'forLoop');
168 | let name = validateNode(node.children[0], 'identifier');
169 | return new ForLoopVarDef(node, name.allText, node.children[1]);
170 | }
171 |
172 | export function createTypeDef(node: HeronAstNode): TypeDef {
173 | validateNode(node, 'typeDef');
174 | let name = node.children[0].allText;
175 | return new TypeDef(node, name);
176 | }
177 |
178 | export function getDef(node: HeronAstNode, typeName: string): T {
179 | if (!node) throw new Error("Node is missing");
180 | let def = node.def;
181 | if (!def) {
182 | throwError(node, "No definition associated with node");
183 | throw new Error("Unexpected code path");
184 | }
185 | const ctor = (def.constructor as any)['name'] as string;
186 | if (ctor !== typeName)
187 | throwError(node, "Incorrect definition type, expected " + typeName + " was " + ctor);
188 | return def as T;
189 | }
--------------------------------------------------------------------------------
/input/sandbox/noise.heron:
--------------------------------------------------------------------------------
1 | // https://thebookofshaders.com/12/
2 | // https://github.com/ashima/webgl-noise/blob/master/src/classicnoise2D.glsl
3 | // https://github.com/ashima/webgl-noise/blob/master/src/cellular2D.glsl
4 | // https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83
5 |
6 | /*
7 | Improving Noise, Ken Perlin http://mrl.nyu.edu/~perlin/paper445.pdf
8 |
9 | Perlin Noise, Hugo Elias http://freespace.virgin.net/hugo.elias/models/m_perlin.htm
10 |
11 | Implementation of Perlin Noise on GPU, Leena Kora http://www.sci.utah.edu/~leenak/IndStudy_reportfall/Perlin%20Noise%20on%20GPU.html
12 |
13 | Implementing Improved Perlin Noise, Simon Green http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter26.html
14 |
15 | Perlin Noise http://mines.lumpylumpy.com/Electronics/Computers/Software/Cpp/Graphics/Bitmap/Textures/Noise/Perlin.php#.U7Fvs41dVhs
16 |
17 | Simplex Noise http://mines.lumpylumpy.com/Electronics/Computers/Software/Cpp/Graphics/Bitmap/Textures/Noise/Simplex.php#.U7FwI41dVhs
18 |
19 | Voronoise http://mines.lumpylumpy.com/Electronics/Computers/Software/Cpp/Graphics/Bitmap/Textures/Noise/Voronoi.php#.U7FwM41dVhs
20 |
21 | Voronoise, inigo quilez http://iquilezles.org/www/articles/voronoise/voronoise.htm
22 |
23 | Voronoi Noise, http://www.pixeleuphoria.com/node/34
24 |
25 | Mosaic Noise http://mines.lumpylumpy.com/Electronics/Computers/Software/Cpp/Graphics/Bitmap/Textures/Noise/Mosaic.php#.U7FwZo1dVhs
26 |
27 | Inigo Quilez http://iquilezles.org/www/articles/morenoise/morenoise.htm
28 |
29 | Examples:
30 |
31 | Andrew Baldwin http://thndl.com/?15
32 |
33 | https://code.google.com/archive/p/fractalterraingeneration/wikis/Simplex_Noise.wiki
34 | https://code.google.com/archive/p/fractalterraingeneration/wikis/Perlin_Noise.wiki
35 | https://code.google.com/archive/p/fractalterraingeneration/wikis/Erosion_Models.wiki
36 | https://code.google.com/archive/p/fractalterraingeneration/wikis/Value_Noise.wiki
37 | https://code.google.com/archive/p/fractalterraingeneration/wikis/Diamond_Square.wiki
38 | https://code.google.com/archive/p/fractalterraingeneration/wikis/Fractional_Brownian_Motion.wiki
39 | https://code.google.com/archive/p/fractalterraingeneration/wikis/Midpoint_Displacement.wiki
40 | https://code.google.com/archive/p/fractalterraingeneration/wikis/Cell_Noise.wiki
41 | https://www.redblobgames.com/maps/terrain-from-noise/
42 | https://www.redblobgames.com/articles/noise/2d/#spectrum
43 | https://thebookofshaders.com/edit.php#12/vorono-01.frag
44 | */
45 |
46 | module noise
47 | {
48 | // Begin IQ's simplex noise:
49 | // The MIT License
50 | // Copyright © 2013 Inigo Quilez
51 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
52 |
53 | function noise( p )
54 | {
55 | function hash( p )
56 | {
57 | // Note from IQ
58 | // replace this by something better
59 | p = vec2( dot(p,vec2(127.1,311.7)),
60 | dot(p,vec2(269.5,183.3)) );
61 |
62 | return -1.0 + 2.0*fract(sin(p)*43758.5453123);
63 | }
64 |
65 | const K1 = 0.366025404; // (sqrt(3)-1)/2;
66 | const K2 = 0.211324865; // (3-sqrt(3))/6;
67 |
68 | const i = floor( p + (p.x+p.y)*K1 );
69 |
70 | const a = p - i + (i.x+i.y)*K2;
71 | const o = step(a.yx,a.xy);
72 | const b = a - o + K2;
73 | const c = a - 1.0 + 2.0*K2;
74 |
75 | const h = max( 0.5-vec3(dot(a,a), dot(b,b), dot(c,c) ), 0.0 );
76 |
77 | const n = h*h*h*h*vec3( dot(a,hash(i+0.0)), dot(b,hash(i+o)), dot(c,hash(i+1.0)));
78 |
79 | return dot( n, vec3(70.0) );
80 | }
81 | }
82 |
83 | /*
84 | // Begin: https://github.com/ashima/webgl-noise/blob/master/src/cellular2D.glsl
85 |
86 | // Cellular noise ("Worley noise") in 2D in GLSL.
87 | // Copyright (c) Stefan Gustavson 2011-04-19. All rights reserved.
88 | // This code is released under the conditions of the MIT license.
89 | // See LICENSE file for details.
90 | // https://github.com/stegu/webgl-noise
91 |
92 | function modulo(x, y)
93 | = x - floor(x * (1.0.vector / y)) * y;
94 |
95 | // Permutation polynomial: (34x^2 + x) mod 289
96 | function permute(x)
97 | = (34.0 * x + 1.0) * x).modulo(289);
98 |
99 | // Cellular noise, returning F1 and F2 in a vec2.
100 | // Standard 3x3 search window for good F1 and F2 values
101 | function cellular(vec2 P) {
102 | var K = 1.0 / 7.0;
103 | var Ko = 3.0 / 7.0;
104 | var jitter = 1.0; // Less gives more regular pattern
105 | var Pi = mod289(floor(P));
106 | var Pf = fract(P);
107 | var oi = vec3(-1.0, 0.0, 1.0);
108 | var of = vec3(-0.5, 0.5, 1.5);
109 | var px = permute(Pi.x + oi);
110 | var p = permute(px.x + Pi.y + oi); // p11, p12, p13
111 | var ox = fract(p*K) - Ko;
112 | var oy = mod7(floor(p*K))*K - Ko;
113 | var dx = Pf.x + 0.5 + jitter*ox;
114 | vec3 dy = Pf.y - of + jitter*oy;
115 | vec3 d1 = dx * dx + dy * dy; // d11, d12 and d13, squared
116 | p = permute(px.y + Pi.y + oi); // p21, p22, p23
117 | ox = fract(p*K) - Ko;
118 | oy = mod7(floor(p*K))*K - Ko;
119 | dx = Pf.x - 0.5 + jitter*ox;
120 | dy = Pf.y - of + jitter*oy;
121 | var d2 = dx * dx + dy * dy; // d21, d22 and d23, squared
122 | p = permute(px.z + Pi.y + oi); // p31, p32, p33
123 | ox = fract(p*K) - Ko;
124 | oy = mod7(floor(p*K))*K - Ko;
125 | dx = Pf.x - 1.5 + jitter*ox;
126 | dy = Pf.y - of + jitter*oy;
127 | var d3 = dx * dx + dy * dy; // d31, d32 and d33, squared
128 |
129 | // Sort out the two smallest distances (F1, F2)
130 | var d1a = min(d1, d2);
131 | d2 = max(d1, d2); // Swap to keep candidates for F2
132 | d2 = min(d2, d3); // neither F1 nor F2 are now in d3
133 | d1 = min(d1a, d2); // F1 is now in d1
134 | d2 = max(d1a, d2); // Swap to keep candidates for F2
135 | d1.xy = (d1.x < d1.y) ? d1.xy : d1.yx; // Swap if smaller
136 | d1.xz = (d1.x < d1.z) ? d1.xz : d1.zx; // F1 is in d1.x
137 | d1.yz = min(d1.yz, d2.yz); // F2 is now not in d2.yz
138 | d1.y = min(d1.y, d1.z); // nor in d1.z
139 | d1.y = min(d1.y, d2.x); // F2 is in d1.y, we're done.
140 | return sqrt(d1.xy);
141 | }
142 |
143 | // End cellular noise
144 | */
145 |
--------------------------------------------------------------------------------
/src/heron-statement.ts:
--------------------------------------------------------------------------------
1 | import { VarDef } from "./heron-defs";
2 | import { throwError, HeronAstNode, wrapInCompoundStatement } from "./heron-ast-rewrite";
3 | import { Expr, getExpr } from "./heron-expr";
4 | import { HeronType } from "./heron-types";
5 |
6 | export class Statement {
7 | constructor(
8 | public readonly node: HeronAstNode,
9 | )
10 | { node.statement = this; }
11 |
12 | // Added as a post-process step.
13 | type: HeronType|null = null
14 | }
15 |
16 | export class CompoundStatement extends Statement {
17 | constructor(
18 | public readonly node: HeronAstNode,
19 | public readonly statements: Statement[]
20 | )
21 | { super(node); }
22 | }
23 |
24 | export class IfStatement extends Statement {
25 | constructor(
26 | public readonly node: HeronAstNode,
27 | public readonly condition: Expr,
28 | public readonly onTrue: CompoundStatement,
29 | public readonly onFalse: CompoundStatement,
30 | )
31 | {
32 | super(node);
33 | if (!condition)
34 | throwError(node, "Missing expression condition");
35 | if (onFalse instanceof CompoundStatement && onFalse.statements[1] === this)
36 | throw new Error("Recursion error");
37 | }
38 | }
39 |
40 | export class ReturnStatement extends Statement {
41 | constructor(
42 | public readonly node: HeronAstNode,
43 | public readonly expr: Expr|null,
44 | )
45 | { super(node); }
46 | }
47 |
48 | export class ContinueStatement extends Statement {
49 | constructor(
50 | public readonly node: HeronAstNode,
51 | )
52 | { super(node); }
53 | }
54 |
55 | export class BreakStatement extends Statement {
56 | constructor(
57 | public readonly node: HeronAstNode,
58 | )
59 | { super(node); }
60 | }
61 |
62 | export class ExprStatement extends Statement {
63 | constructor(
64 | public readonly node: HeronAstNode,
65 | public readonly expr: Expr,
66 | )
67 | { super(node); }
68 | }
69 |
70 | export class ForStatement extends Statement {
71 | constructor(
72 | public readonly node: HeronAstNode,
73 | public readonly identifier: string,
74 | public readonly array: Expr,
75 | public readonly loop: CompoundStatement,
76 | )
77 | { super(node); }
78 | }
79 |
80 | export class DoStatement extends Statement {
81 | constructor(
82 | public readonly node: HeronAstNode,
83 | public readonly condition: Expr,
84 | public readonly body: CompoundStatement,
85 | )
86 | { super(node); }
87 | }
88 |
89 | export class WhileStatement extends Statement {
90 | constructor(
91 | public readonly node: HeronAstNode,
92 | public readonly condition: Expr,
93 | public readonly body: CompoundStatement,
94 | )
95 | { super(node); }
96 | }
97 |
98 | export class VarDeclStatement extends Statement {
99 | constructor(
100 | public readonly node: HeronAstNode,
101 | public readonly vars: VarDef[],
102 | )
103 | { super(node); }
104 | }
105 |
106 | export class EmptyStatement extends Statement {
107 | }
108 |
109 | //============================================================
110 | // Functions
111 |
112 | export function createCompoundStatement(node: HeronAstNode): CompoundStatement {
113 | return createStatement(wrapInCompoundStatement(node)) as CompoundStatement;
114 | }
115 |
116 | export function createStatement(node: HeronAstNode): Statement {
117 | if (node.statement)
118 | return node.statement;
119 |
120 | switch (node.name) {
121 | case 'emptyStatement':
122 | return new EmptyStatement(node);
123 | case 'compoundStatement':
124 | return new CompoundStatement(node, node.children.map(createStatement));
125 | case 'ifStatement':
126 | return new IfStatement(node,
127 | getExpr(node.children[0]),
128 | createCompoundStatement(node.children[1]),
129 | createCompoundStatement(node.children[2]));
130 | case 'returnStatement':
131 | return new ReturnStatement(node,
132 | node.children.length > 0 ? getExpr(node.children[0]) : null);
133 | case 'continueStatement':
134 | return new ContinueStatement(node);
135 | case 'breakStatement':
136 | return new BreakStatement(node);
137 | case 'forLoop':
138 | return new ForStatement(node, node.children[0].allText,
139 | getExpr(node.children[1]), createCompoundStatement(node.children[2]));
140 | case 'doLoop':
141 | return new DoStatement(node,
142 | getExpr(node.children[1]), createCompoundStatement(node.children[0]));
143 | case 'whileLoop':
144 | return new WhileStatement(node,
145 | getExpr(node.children[0]), createCompoundStatement(node.children[1]));
146 | case 'varDeclStatement':
147 | return new VarDeclStatement(node, node.children[0].children.map(n => n.def as VarDef));
148 | case 'exprStatement':
149 | return new ExprStatement(node, getExpr(node.children[0]));
150 | }
151 | }
152 |
153 | // Returns true if a statement is a loop break statemnt
154 | export function isLoopBreak(st: Statement): boolean {
155 | return st instanceof ReturnStatement || st instanceof BreakStatement;
156 | }
157 |
158 | // Returns the last statement in a compound statement group
159 | export function lastStatement(st: CompoundStatement): Statement {
160 | if (st.statements.length === 0)
161 | return null;
162 | return st.statements[st.statements.length - 1];
163 | }
164 |
165 | export function hasLoopBreak(st: Statement): boolean {
166 | if (isLoopBreak(st))
167 | return true;
168 |
169 | if (st instanceof CompoundStatement) {
170 | return st.statements.some(hasLoopBreak);
171 | }
172 | else if (st instanceof IfStatement) {
173 | return hasLoopBreak(st.onTrue) || hasLoopBreak(st.onFalse);
174 | }
175 |
176 | return false;
177 | }
178 |
179 | // Put If statements into tail position of a loop
180 | export function rewriteIfStatements(node: HeronAstNode) {
181 | throw new Error("This can create recursion error");
182 | /*
183 | let st = node.statement;
184 | if (!st) return;
185 | if (st instanceof CompoundStatement) {
186 | for (var i=st.statements.length-2; i >= 0; --i) {
187 | let c = st.statements[i];
188 | if (c instanceof IfStatement) {
189 | if (!isLoopBreak(lastStatement(c.onTrue)))
190 | c.onTrue.statements.push(...st.statements.slice(i));
191 | if (!isLoopBreak(lastStatement(c.onFalse)))
192 | c.onFalse.statements.push(...st.statements.slice(i));
193 |
194 | // Delete the statements after the current
195 | st.statements.splice(i+1, st.statements.length - i - 1);
196 | }
197 | else if (st instanceof BreakStatement) {
198 | // Delete the statements after the current
199 | st.statements.splice(i+1, st.statements.length - i - 1);
200 | }
201 | }
202 | // Check the algorithm
203 | for (var i=0; i < st.statements.length - 1; ++i) {
204 | if (st.statements[i] instanceof IfStatement)
205 | throw new Error("Internal error: found an If statement in a compound statement that was not in tail position");
206 | }
207 | }
208 | */
209 | }
210 |
211 |
212 |
--------------------------------------------------------------------------------
/demo/js/ParametricGeometries.js.download:
--------------------------------------------------------------------------------
1 | /*
2 | * @author zz85
3 | *
4 | * Experimenting of primitive geometry creation using Surface Parametric equations
5 | *
6 | */
7 |
8 | THREE.ParametricGeometries = {
9 |
10 | klein: function ( v, u, optionalTarget ) {
11 |
12 | var result = optionalTarget || new THREE.Vector3();
13 |
14 | u *= Math.PI;
15 | v *= 2 * Math.PI;
16 |
17 | u = u * 2;
18 | var x, y, z;
19 | if ( u < Math.PI ) {
20 |
21 | x = 3 * Math.cos( u ) * ( 1 + Math.sin( u ) ) + ( 2 * ( 1 - Math.cos( u ) / 2 ) ) * Math.cos( u ) * Math.cos( v );
22 | z = - 8 * Math.sin( u ) - 2 * ( 1 - Math.cos( u ) / 2 ) * Math.sin( u ) * Math.cos( v );
23 |
24 | } else {
25 |
26 | x = 3 * Math.cos( u ) * ( 1 + Math.sin( u ) ) + ( 2 * ( 1 - Math.cos( u ) / 2 ) ) * Math.cos( v + Math.PI );
27 | z = - 8 * Math.sin( u );
28 |
29 | }
30 |
31 | y = - 2 * ( 1 - Math.cos( u ) / 2 ) * Math.sin( v );
32 |
33 | return result.set( x, y, z );
34 |
35 | },
36 |
37 | plane: function ( width, height ) {
38 |
39 | return function ( u, v, optionalTarget ) {
40 |
41 | var result = optionalTarget || new THREE.Vector3();
42 |
43 | var x = u * width;
44 | var y = 0;
45 | var z = v * height;
46 |
47 | return result.set( x, y, z );
48 |
49 | };
50 |
51 | },
52 |
53 | mobius: function ( u, t, optionalTarget ) {
54 |
55 | var result = optionalTarget || new THREE.Vector3();
56 |
57 | // flat mobius strip
58 | // http://www.wolframalpha.com/input/?i=M%C3%B6bius+strip+parametric+equations&lk=1&a=ClashPrefs_*Surface.MoebiusStrip.SurfaceProperty.ParametricEquations-
59 | u = u - 0.5;
60 | var v = 2 * Math.PI * t;
61 |
62 | var x, y, z;
63 |
64 | var a = 2;
65 |
66 | x = Math.cos( v ) * ( a + u * Math.cos( v / 2 ) );
67 | y = Math.sin( v ) * ( a + u * Math.cos( v / 2 ) );
68 | z = u * Math.sin( v / 2 );
69 |
70 | return result.set( x, y, z );
71 |
72 | },
73 |
74 | mobius3d: function ( u, t, optionalTarget ) {
75 |
76 | var result = optionalTarget || new THREE.Vector3();
77 |
78 | // volumetric mobius strip
79 |
80 | u *= Math.PI;
81 | t *= 2 * Math.PI;
82 |
83 | u = u * 2;
84 | var phi = u / 2;
85 | var major = 2.25, a = 0.125, b = 0.65;
86 |
87 | var x, y, z;
88 |
89 | x = a * Math.cos( t ) * Math.cos( phi ) - b * Math.sin( t ) * Math.sin( phi );
90 | z = a * Math.cos( t ) * Math.sin( phi ) + b * Math.sin( t ) * Math.cos( phi );
91 | y = ( major + x ) * Math.sin( u );
92 | x = ( major + x ) * Math.cos( u );
93 |
94 | return result.set( x, y, z );
95 |
96 | }
97 |
98 | };
99 |
100 |
101 | /*********************************************
102 | *
103 | * Parametric Replacement for TubeGeometry
104 | *
105 | *********************************************/
106 |
107 | THREE.ParametricGeometries.TubeGeometry = function ( path, segments, radius, segmentsRadius, closed, debug ) {
108 |
109 | this.path = path;
110 | this.segments = segments || 64;
111 | this.radius = radius || 1;
112 | this.segmentsRadius = segmentsRadius || 8;
113 | this.closed = closed || false;
114 | if ( debug ) this.debug = new THREE.Object3D();
115 |
116 | var scope = this, numpoints = this.segments + 1;
117 |
118 | var frames = path.computeFrenetFrames( segments, closed ),
119 | tangents = frames.tangents,
120 | normals = frames.normals,
121 | binormals = frames.binormals;
122 |
123 | // proxy internals
124 |
125 | this.tangents = tangents;
126 | this.normals = normals;
127 | this.binormals = binormals;
128 |
129 | var ParametricTube = function ( u, v, optionalTarget ) {
130 |
131 | var result = optionalTarget || new THREE.Vector3();
132 |
133 | v *= 2 * Math.PI;
134 |
135 | var i = u * ( numpoints - 1 );
136 | i = Math.floor( i );
137 |
138 | var pos = path.getPointAt( u );
139 |
140 | var tangent = tangents[ i ];
141 | var normal = normals[ i ];
142 | var binormal = binormals[ i ];
143 |
144 | if ( scope.debug ) {
145 |
146 | scope.debug.add( new THREE.ArrowHelper( tangent, pos, radius, 0x0000ff ) );
147 | scope.debug.add( new THREE.ArrowHelper( normal, pos, radius, 0xff0000 ) );
148 | scope.debug.add( new THREE.ArrowHelper( binormal, pos, radius, 0x00ff00 ) );
149 |
150 | }
151 |
152 | var cx = - scope.radius * Math.cos( v ); // TODO: Hack: Negating it so it faces outside.
153 | var cy = scope.radius * Math.sin( v );
154 |
155 | pos.x += cx * normal.x + cy * binormal.x;
156 | pos.y += cx * normal.y + cy * binormal.y;
157 | pos.z += cx * normal.z + cy * binormal.z;
158 |
159 | return result.copy( pos );
160 |
161 | };
162 |
163 | THREE.ParametricGeometry.call( this, ParametricTube, segments, segmentsRadius );
164 |
165 | };
166 |
167 | THREE.ParametricGeometries.TubeGeometry.prototype = Object.create( THREE.Geometry.prototype );
168 | THREE.ParametricGeometries.TubeGeometry.prototype.constructor = THREE.ParametricGeometries.TubeGeometry;
169 |
170 |
171 | /*********************************************
172 | *
173 | * Parametric Replacement for TorusKnotGeometry
174 | *
175 | *********************************************/
176 | THREE.ParametricGeometries.TorusKnotGeometry = function ( radius, tube, segmentsT, segmentsR, p, q ) {
177 |
178 | this.radius = radius || 200;
179 | this.tube = tube || 40;
180 | this.segmentsT = segmentsT || 64;
181 | this.segmentsR = segmentsR || 8;
182 | this.p = p || 2;
183 | this.q = q || 3;
184 |
185 | function TorusKnotCurve() {
186 |
187 | THREE.Curve.call( this );
188 |
189 | }
190 |
191 | TorusKnotCurve.prototype = Object.create( THREE.Curve.prototype );
192 | TorusKnotCurve.prototype.constructor = TorusKnotCurve;
193 |
194 | TorusKnotCurve.prototype.getPoint = function ( t, optionalTarget ) {
195 |
196 | var point = optionalTarget || new THREE.Vector3();
197 |
198 | t *= Math.PI * 2;
199 |
200 | var r = 0.5;
201 |
202 | var x = ( 1 + r * Math.cos( q * t ) ) * Math.cos( p * t );
203 | var y = ( 1 + r * Math.cos( q * t ) ) * Math.sin( p * t );
204 | var z = r * Math.sin( q * t );
205 |
206 | return point.set( x, y, z ).multiplyScalar( radius );
207 |
208 | };
209 |
210 | var segments = segmentsT;
211 | var radiusSegments = segmentsR;
212 | var extrudePath = new TorusKnotCurve();
213 |
214 | THREE.ParametricGeometries.TubeGeometry.call( this, extrudePath, segments, tube, radiusSegments, true, false );
215 |
216 | };
217 |
218 | THREE.ParametricGeometries.TorusKnotGeometry.prototype = Object.create( THREE.Geometry.prototype );
219 | THREE.ParametricGeometries.TorusKnotGeometry.prototype.constructor = THREE.ParametricGeometries.TorusKnotGeometry;
220 |
221 |
222 | /*********************************************
223 | *
224 | * Parametric Replacement for SphereGeometry
225 | *
226 | *********************************************/
227 | THREE.ParametricGeometries.SphereGeometry = function ( size, u, v ) {
228 |
229 | function sphere( u, v, optionalTarget ) {
230 |
231 | var result = optionalTarget || new THREE.Vector3();
232 |
233 | u *= Math.PI;
234 | v *= 2 * Math.PI;
235 |
236 | var x = size * Math.sin( u ) * Math.cos( v );
237 | var y = size * Math.sin( u ) * Math.sin( v );
238 | var z = size * Math.cos( u );
239 |
240 | return result.set( x, y, z );
241 |
242 | }
243 |
244 | THREE.ParametricGeometry.call( this, sphere, u, v );
245 |
246 | };
247 |
248 | THREE.ParametricGeometries.SphereGeometry.prototype = Object.create( THREE.Geometry.prototype );
249 | THREE.ParametricGeometries.SphereGeometry.prototype.constructor = THREE.ParametricGeometries.SphereGeometry;
250 |
251 |
252 | /*********************************************
253 | *
254 | * Parametric Replacement for PlaneGeometry
255 | *
256 | *********************************************/
257 |
258 | THREE.ParametricGeometries.PlaneGeometry = function ( width, depth, segmentsWidth, segmentsDepth ) {
259 |
260 | function plane( u, v, optionalTarget ) {
261 |
262 | var result = optionalTarget || new THREE.Vector3();
263 |
264 | var x = u * width;
265 | var y = 0;
266 | var z = v * depth;
267 |
268 | return result.set( x, y, z );
269 |
270 | }
271 |
272 | THREE.ParametricGeometry.call( this, plane, segmentsWidth, segmentsDepth );
273 |
274 | };
275 |
276 | THREE.ParametricGeometries.PlaneGeometry.prototype = Object.create( THREE.Geometry.prototype );
277 | THREE.ParametricGeometries.PlaneGeometry.prototype.constructor = THREE.ParametricGeometries.PlaneGeometry;
278 |
--------------------------------------------------------------------------------
/src/heron-to-html.ts:
--------------------------------------------------------------------------------
1 | import { Myna as m } from "myna-parser";
2 | import { HeronAstNode, visitAst } from "./heron-ast-rewrite";
3 | import { g } from "./heron-parser";
4 | import { Myna } from "myna-parser/myna";
5 | import { Module, Package } from "./heron-package";
6 | import { parseFile } from "./heron-compiler";
7 | import { Lookup, normalizeType, PolyType, isTypeConstant, alphabetizeVarNames, TypeVariable } from "./type-system";
8 | import { FunctionSet, HeronType, getFuncArgTypes, getFuncReturnType } from "./heron-types";
9 |
10 | const heronKeywords = [
11 | 'var', 'in', 'for', 'while', 'do', 'if', 'else', 'continue', 'return', 'type', 'import', 'function', 'intrinsic', 'type', 'module', 'language'
12 | ];
13 |
14 | // This grammar is used for tokenizing text content not explicitly represented
15 | // by nodes in the Heron AST for syntax coloring
16 |
17 | const nodeLookup: Lookup = {};
18 |
19 | declare var require;
20 | const fs = require('fs');
21 | const path = require('path');
22 |
23 | class TokenGrammar
24 | {
25 | fullComment = g.fullComment.ast;
26 | lineComment = g.lineComment.ast;
27 | keyword = m.keywords(...heronKeywords).ast;
28 | blankSpace = g.blankSpace.ast;
29 | operator = g.opSymbol.oneOrMore.ast;
30 | delimiter = m.char('{}();,').oneOrMore.ast;
31 | unknown = m.advance.ast;
32 | token = m.choice(this.fullComment, this.lineComment, this.keyword, this.delimiter, this.blankSpace, this.operator);
33 | plainText = m.advance.ast;
34 | tokens = m.choice(this.token, this.plainText).zeroOrMore;
35 | };
36 |
37 | const tokenGrammar = new TokenGrammar();
38 | Myna.registerGrammar("heronTokens", tokenGrammar, tokenGrammar.tokens);
39 | const parser = Myna.parsers['heronTokens'];
40 |
41 |
42 | function span(className: string, contents: string): string {
43 | return `${contents} `;
44 | }
45 |
46 | function div(className: string, contents: string): string {
47 | return `${contents}
`;
48 | }
49 |
50 | function visitToken(node: Myna.AstNode, lines: string[]) {
51 | const content = toHtml(node.allText);
52 | switch (node.name)
53 | {
54 | case "blankSpace":
55 | case "unknown":
56 | case "plainText":
57 | lines.push(content);
58 | break;
59 | case "lineComment":
60 | lines.push(span(node.name, content.trim()));
61 | break;
62 | case "fullComment":
63 | case "delimiter":
64 | default:
65 | lines.push(span(node.name, content));
66 | break;
67 | }
68 | }
69 |
70 | function textTokenizer(text: string, lines: string[]) {
71 | const ast = parser(text);
72 | for (const c of ast.children)
73 | visitToken(c, lines);
74 | }
75 |
76 | function toHtml(text: string): string {
77 | return text.replace(/&/g, "&")
78 | .replace(//g, ">")
80 | .replace(/"/g, """)
81 | .replace(/'/g, "'");
82 | }
83 |
84 | export function nodeToHtml(source: string, node: HeronAstNode, parent: HeronAstNode, lastPos: number, lines: string[]): number {
85 | if (!node)
86 | return lastPos;
87 | if (node.input !== source)
88 | return lastPos;
89 | if (node.start < lastPos)
90 | throw new Error("Internal error: backing up on input. Should not be possible");
91 |
92 | // If we are starting this node after the last one ended, we have some plain text to tokenize
93 | if (node.start > lastPos)
94 | textTokenizer(source.substr(lastPos, node.start - lastPos), lines);
95 |
96 | // Check for leaf nodes
97 | switch (node.name) {
98 | case "string":
99 | case "number":
100 | case "bool":
101 | case "typename":
102 | case "identifier":
103 | case "langVer":
104 | case "moduleName":
105 | case "funcParamName":
106 | case "funcName":
107 | case "varName":
108 | case "varNameDecl":
109 | lines.push(span(node.name, toHtml(node.allText)));
110 | return node.end;
111 | }
112 |
113 | let closeSpan = false;
114 | switch (node.name) {
115 | case "emptyStatement":
116 | case "compoundStatement":
117 | case "ifStatement":
118 | case "returnStatement":
119 | case "continueStatement":
120 | case "breakStatement":
121 | case "forLoop":
122 | case "doLoop":
123 | case "whileLoop":
124 | case "varDeclStatement":
125 | //case "funcDef":
126 | //case "intrinsicDef":
127 | //case "typeDef":
128 | //case "importStatement":
129 | case "exprStatement":
130 | lines.push("");
131 | closeSpan = true;
132 | break;
133 | }
134 |
135 | // Check for the special case of a functionDef
136 | const node2 = nodeLookup[node.start];
137 | if (node.name === 'funcDef' && node2 && node2.def) {
138 | let t = toHeronTypeString(alphabetizeVarNames(node2.def.type));
139 | t = toHtml(t);
140 | lines.push('');
141 | lines.push(`${t} `);
142 | closeSpan = true;
143 | }
144 |
145 | // A non-leaf node. Iterate over children.
146 | // If a child starts after the currentPos, it will add the necessary tokens
147 | let curPos = node.start;
148 | for (const c of node.children)
149 | curPos = nodeToHtml(source, c, node, curPos, lines);
150 |
151 | if (closeSpan)
152 | lines.push(" ");
153 |
154 | // If we have to add some padding leaf nodes, we will do that here.
155 | if (curPos < node.end)
156 | textTokenizer(source.substr(curPos, node.end - curPos), lines);
157 | return node.end;
158 | }
159 |
160 | export function heronNodeToHtml(node: HeronAstNode): string {
161 | const lines = [];
162 | const input = node.input;
163 | const r = nodeToHtml(input, node, null, 0, lines)
164 | if (r !== input.length)
165 | throw new Error("Did not parse to the end");
166 | return lines.join("");
167 | }
168 |
169 | export function heronModuleToHtml(module: Module): string {
170 | // This creates an unmodified parse tree.
171 | const ast = parseFile(module.file.filePath);
172 |
173 | // Visit the modified tree, adding to the lookup table every modified node (if corresponding to the new position)
174 | visitAst(module.node, n => {
175 | if (!n.original && n.input === ast.input)
176 | nodeLookup[n.start] = n;
177 | });
178 |
179 | return heronNodeToHtml(ast);
180 | }
181 |
182 | export function heronPackageToHtml(pkg: Package)
183 | {
184 | for (const m of pkg.modules) {
185 | const html = heronModuleToHtml(m);
186 | //const fileName = path.join('src-html', m.file.filePath + ".html");
187 | const fileName = m.file.filePath.replace('.heron', '.html').replace('input', 'source-browser');
188 | const url = 'https://github.com/cdiggins/heron-language/tree/master/input/' + path.basename(m.file.filePath);
189 | const fileContents = makeHtml(m.name, html, url);
190 | fs.writeFileSync(fileName, fileContents);
191 | }
192 | }
193 |
194 | export function toHeronTypeString(t: HeronType) {
195 | if (t instanceof FunctionSet)
196 | {
197 | throw new Error("Function sets not supported");
198 | }
199 | else
200 | if (t instanceof PolyType) {
201 | const first = t.types[0];
202 | if (isTypeConstant(first, "Func")) {
203 | const args = getFuncArgTypes(t);
204 | const ret = getFuncReturnType(t);
205 | return "(" + args.map(toHeronTypeString).join(" ") + " -> " + toHeronTypeString(ret) + ")";
206 | }
207 | else {
208 | return first.toString() + "<" + t.types.slice(1).map(toHeronTypeString).join(", ") + ">";
209 | }
210 | }
211 | else if (t instanceof TypeVariable || typeof(t) === 'string') {
212 | return "'" + t;
213 | }
214 | else {
215 | return t.toString();
216 | }
217 | }
218 |
219 | function makeHtml(name, html, url) {
220 | return `${name}
221 |
222 |
223 |
224 |
225 |
244 |
248 |
249 | `;
250 | }
--------------------------------------------------------------------------------
/src/tests.ts:
--------------------------------------------------------------------------------
1 | import * as Myna from "myna-parser";
2 | import { heronGrammar } from './heron-parser';
3 | import { HeronToJs, funcDefName } from "./heron-to-js";
4 | import { parseLocation } from "./heron-ast-rewrite";
5 | import { createPackage } from "./heron-compiler";
6 | import { Ref } from "./heron-refs";
7 | import { Package } from "./heron-package";
8 | import { computeFuncType } from "./heron-types";
9 | import { parseType } from "./type-parser";
10 | import { normalizeType } from "./type-system";
11 | import { heronModuleToHtml, heronPackageToHtml } from "./heron-to-html";
12 |
13 | const m = Myna.Myna;
14 | const g = heronGrammar;
15 |
16 | declare var require;
17 | const assert = require('assert');
18 | const path = require('path');
19 |
20 | // Assure that two ASTs have the same shape
21 | // For example if I generate some text, and re-parse it.
22 | /* TOOD: use or throw away.
23 | function compareAst(a, b) {
24 | if (!a && b || a && !b)
25 | return false;
26 | if (a.children.length != b.children.length)
27 | return false;
28 | if (a.children.length === 0)
29 | return a.allText === b.allText;
30 | if (a.name !== b.name)
31 | return false;
32 | for (var i=0; i < a.children.length; ++i)
33 | if (!compareAst(a.children[i], b.children[i]))
34 | return false;
35 | return true;
36 | }
37 | */
38 |
39 | // Tests parsing an individual rule against the input input text, and returns an object
40 | // representing the result of running the test
41 | function testParse(rule, assert, text, shouldPass) {
42 | if (shouldPass == undefined) shouldPass = true;
43 | let result = m.failed;
44 | let err = undefined;
45 | try {
46 | let node = m.parse(rule, text);
47 | if (node)
48 | result = node.end;
49 | }
50 | catch (e) {
51 | err = e;
52 | }
53 |
54 | let testResult = {
55 | name : rule.toString() + ' with input "' + text + '"',
56 | description : result + "/" + text.length,
57 | negative : !shouldPass,
58 | success : (result === text.length) !== !shouldPass,
59 | error : err,
60 | ruleDescr : rule.type + ": " + rule.toString(),
61 | rule : rule
62 | };
63 |
64 | if (!testResult.success)
65 | console.log(testResult);
66 |
67 | assert.ok(testResult.success, testResult.name + (shouldPass ? "" : " should fail"));
68 | }
69 |
70 | const ruleTests = [
71 | [g.comment, ['/* abc */', '// abc \n', '/* abc */ /* def */ '], ['abc', '', '/*']],
72 | [g.funCall, ['(a, b)', '(a)', '(F )', '()'], []],
73 | [g.expr, ['42', '3+4', '3 + 4', '3 * (2 + 4)', '3 * 2 + 4', 'a', 'a++', 'f(1,2)', 'f(3, 5)', 'f()', 'f(12)', 'hx > 0.0'], []],
74 | [g.expr, ['0..1', '0 .. 1', 'f(a .. b)'], ['xs[0:5]', 'xs[4:0:-1]']],
75 | [g.expr, ['x => 42', '(x) => 13', 'x=>3', 'x => { return 42; }', '(x, y) => x * y'], []],
76 | [g.expr, ['(3)', '(3 + 2)', '(c)', '(a)+(b)', 'a+(b)', 'a+b+c', 'a?b:c', 'a?b+c:d', 'a?b:c+d','a?(b):c', '(a)?b:c'], []],
77 | [g.expr, ['a?b:1(c)'], ['f > max ? max : (f < min ? min) : f']],
78 | [g.expr, ['op+', 'f(op+)', 'f(0, op+)'], []],
79 | [g.expr, ['(r1+r2*cos(v))', '(r1 + r2 * cos(v))', '( r1 + r2 * cos(v) )', '( r1 + r2 * cos(v) ) * cos(u)'], []],
80 | [g.expr, ['a?b:c', 'a?b:c?d:e', 'a?b:(c3?b:c', 'a > 3 ? b : c', 'f > max ? max : (f < min ? min : f)'], []],
81 | [g.statement, [
82 | 'var x = 0;',
83 | 'f();',
84 | 'if (x) f();',
85 | 'if (x) f(); else g();',
86 | 'return;',
87 | ';',
88 | 'var test = 1;\n /* */',
89 | 'return p;',
90 | 'if (hx) return p;',
91 | 'if (hx > 0.0) return p;',
92 | 'if(hx > 0.0) return p;',
93 | ],
94 | [
95 | 'f()',
96 | 'return 42',
97 | 'g',
98 | ';;',
99 | ]]
100 | ];
101 |
102 | export function testParsingRules() {
103 | for (let ruleTest of ruleTests) {
104 | let rule = ruleTest[0];
105 | for (let passInput of ruleTest[1])
106 | testParse(rule, assert, passInput, true);
107 | for (let failInput of ruleTest[2])
108 | testParse(rule, assert, failInput, false);
109 | }
110 | }
111 |
112 | declare var require;
113 | const fs = require('fs');
114 |
115 | function refDetails(ref: Ref): string {
116 | return `Ref Details
117 | ${parseLocation(ref.node)}
118 | ref = ${ref.toString()}
119 | name = ${ref.name}
120 | node = ${ref.node['id']}
121 | expr = ${ref.node['expr']}
122 | ${ref.defs}`;
123 | }
124 |
125 | export function outputPackageStats(pkg: Package) {
126 | console.log("Files: ");
127 | console.log(pkg.files);
128 | console.log("# Modules : " + pkg.modules.length);
129 | console.log("# Scopes : " + pkg.scopes.length);
130 | console.log("# Defs : " + pkg.defs.length);
131 | console.log("# Usages : " + pkg.refs.length);
132 |
133 | let multiDefs = pkg.refs.filter(r => r.defs.length > 1);
134 | let zeroDefs = pkg.refs.filter(r => r.defs.length == 0);
135 | console.log('# Refs with multiple defs : ' + multiDefs.length)
136 | console.log('# Refs with zero defs : ' + zeroDefs.length)
137 |
138 | for (var d of multiDefs)
139 | console.log(refDetails(d));
140 | }
141 |
142 | function outputFunctionTypes(pkg: Package) {
143 | for (const f of pkg.allFuncDefs) {
144 | let t = computeFuncType(f);
145 | if (f.body) {
146 | const finalType = normalizeType(t);
147 | console.log(f.toString() + " :: " + finalType);
148 | }
149 | }
150 | }
151 |
152 | function testParseType(expr: string) {
153 | const t = parseType(expr);
154 | console.log(expr);
155 | console.log(" : " + t);
156 | }
157 |
158 | export function testParseTypes() {
159 | const typeStrings = [
160 | "(Num Num)",
161 | "(Func 'T0 'T1 R)",
162 | "(Array Num)",
163 | "(Array (Func 'T0 Num))",
164 | "(Func (Array 'T) (Func 'T 'U) (Array 'U))",
165 | "(Func (Array 'T) (Array 'U) (Func 'T 'U 'V) (Array 'V))",
166 | "(Func (Array 'T) (Array 'U) (Func 'T 'U Int 'V) (Array 'V))",
167 | ];
168 | for (const ts of typeStrings)
169 | testParseType(ts);
170 | }
171 |
172 | function tests() {
173 | //testParsingRules();
174 | //testParseTypes();
175 |
176 | let inputFiles = ['geometry-vector3', 'geometry-mesh', 'array', 'test'];
177 | let pkg = createPackage(inputFiles);
178 |
179 | //outputPackageStats(pkg);
180 | /*
181 | for (const sf of pkg.files) {
182 | const outputPath = sf.filePath.substr(0, sf.filePath.lastIndexOf('.')) + '.output.heron';
183 | const text = heronToText(sf.node as HeronAstNode);
184 | fs.writeFileSync(outputPath, text);
185 | }*/
186 | const toJs = new HeronToJs();
187 | for (const m of pkg.modules) {
188 | toJs.visit(m);
189 | }
190 | const now = new Date();
191 | const library = fs.readFileSync(path.join('src', 'js-intrinsics.js'), 'utf-8');
192 | let text = '// Generated using Heron on ' + now.toDateString() + ' ' + now.toTimeString() + '\n';
193 | text += 'var heron = (function () {\n';
194 | text += library + '\n';
195 | text += toJs.cb.toString();
196 | text += '\n';
197 |
198 | // Originally we just exported the main
199 | // const main = pkg.findFunction("main");
200 |
201 | // Now we export every function from the main module
202 | text += 'return {';
203 | const m = pkg.getModule("heron:tests:0.1");
204 | for (const f of m.functions)
205 | text += f.name + ' : ' + funcDefName(f) + ',\n';
206 | text += '};\n';
207 | text += '})();\n';
208 | text += 'heron.main()';
209 | //fs.writeFileSync(path.join(outputFolder, 'output.js'), text);
210 | fs.writeFileSync(path.join('demo', 'output.js'), text);
211 |
212 | //outputPackageStats(pkg);
213 | // find the main entry point and call into it.
214 | let modName = 'heron:tests:0.1';
215 | let mainMod = pkg.getModule(modName);
216 | if (!mainMod)
217 | throw new Error("Could not find module: " + modName);
218 |
219 | //let mainFunc = findFunc(mainMod, 'main');
220 | //if (!mainFunc)
221 | // throw new Error("Could not find entry point function " + modName + "." + mainFunc);
222 | // let evaluator = new Evaluator();
223 |
224 | // Try to figure out the value of all the called functions.
225 | //evaluator.evalFunc(mainFunc);
226 |
227 | // Look at the usages of each parameter in each function.
228 | //analyzeFunctions(pkg);
229 |
230 | // An experiemnt for guessing Traits.
231 | // I shave decided that traits need to be declared.
232 | //outputTraits(pkg);
233 |
234 | outputFunctionTypes(pkg);
235 |
236 | /*
237 | for (const k in intrinsics)
238 | console.log(intrinsics[k].toString());
239 | */
240 |
241 | heronPackageToHtml(pkg);
242 | console.log('Done');
243 | }
244 |
245 | /*
246 | testParseExpr("2 + 3");
247 | testParseCode("2 + 3", g.additiveExpr);
248 | testParseCode("2 + 3", g.relationalExpr);
249 | testParseCode("2 + 3", g.equalityExpr);
250 | testParseCode("2 + 3", g.logicalAndExpr);
251 | testParseCode("2 + 3", g.logicalXOrExpr);
252 | testParseCode("2 + 3", g.logicalOrExpr);
253 | testParseCode("2 + 3", g.rangeExpr);
254 | testParseCode("2 + 3", g.conditionalExpr);
255 | testParseCode("2 + 3", g.assignmentExpr);
256 | */
257 |
258 | //testParseFile('.\\tests\\seascape.heron');
259 | //testParseFile('.\\tests\\stdlib.heron');
260 | //testParseFile('.\\inputs\\geometry-vector3.heron');
261 | //testParseFile('.\\inputs\\intrinsics.heron');
262 | tests();
263 |
264 | declare var process;
265 | process.exit();
--------------------------------------------------------------------------------
/input/sandbox/geometry-matrix4x4.heron:
--------------------------------------------------------------------------------
1 | language heron:std:0.1;
2 |
3 | // https://referencesource.microsoft.com/#System.Numerics/System/Numerics/Matrix4x4.cs
4 | // http://www.euclideanspace.com/maths/algebra/matrix/index.htm
5 |
6 | module cdiggins:geometry:matrix4x4:0.1
7 | {
8 | function matrix(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44) = {
9 | M11=m11; M12=m12; M13=m13; M14=m14;
10 | M21=m21; M22=m22; M23=m23; M24=m24;
11 | M31=m31; M32=m32; M33=m33; M34=m34;
12 | M41=m41; M42=m42; M43=m43; M44=m44;
13 | };
14 |
15 | function matrix_from_cols(c1, c2, c3, c4)
16 | = matrix(c1[0], c2[0], c3[0], c4[0],
17 | c1[1], c2[1], c3[1], c4[1],
18 | c1[2], c2[2], c3[2], c4[2],
19 | c1[3], c2[3], c3[3], c4[3]);
20 |
21 | function matrix_from_rows(r1, r2, r3, r4)
22 | = matrix(r1[0], r1[1], r1[2], r1[3],
23 | r2[0], r2[1], r2[2], r2[3],
24 | r3[0], r3[1], r3[2], r3[3],
25 | r4[0], r4[1], r4[2], r4[3]);
26 |
27 | var identity
28 | = matrix(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1);
29 |
30 | var matrix
31 | = matrix(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0);
32 |
33 | function translation(m)
34 | = vector(m.M41, m.M42, m.M43);
35 |
36 | function translation_matrix(v)
37 | = matrix(1,0,0,0, 0,1,0,0, 0,0,1,0, v.x,v.y,v.z,1);
38 |
39 | function scaling_matrix(v)
40 | = matrix(v.x,0,0,0, 0,v.y,0,0, 0,0,v.z,0, 0,0,0,1);
41 |
42 | function scaling_matrix(s)
43 | = matrix(s,0,0,0, 0,s,0,0, 0,0,s,0, 0,0,0,1);
44 |
45 | function translation_scaling_matrix(t, s)
46 | = matrix(s.x,0,0,0, 0,s.y,0,0, 0,0,s.z,0, t.x,t.y,t.z,1);
47 |
48 | function rotation_x_matrix(r)
49 | = matrix(1,0,0,0, 0,cos(r),sin(r),0, 0,-sin(r),cos(r),0, 0,0,0,1);
50 |
51 | function rotation_y_matrix(r)
52 | = matrix(cos(r),0,-sin(r),0, 0,1,0,0, sin(r),0,cos(r),0, 0,0,0,1);
53 |
54 | function rotation_z_matrix(r)
55 | = matrix(cos(r),sin(r),0,0, -sin(r),cos(r),0,0, 0,0,1,0, 0,0,0,1);
56 |
57 | function rotation_matrix(axis, angle) {
58 | var x = axis.x;
59 | var y = axis.y;
60 | var z = axis.Z;
61 | var sa = sin(angle);
62 | var ca = cos(angle);
63 | var xx = x * x;
64 | var yy = y * y;
65 | var zz = z * z;
66 | var xy = x * y;
67 | var xz = x * z;
68 | var yz = y * z;
69 | return matrix(
70 | xx + ca * (1.0 - xx),
71 | xy - ca * xy + sa * z,
72 | xz - ca * xz - sa * y,
73 | 0.0,
74 | xy - ca * xy - sa * z,
75 | yy + ca * (1.0 - yy),
76 | yz - ca * yz + sa * x,
77 | 0.0,
78 | xz - ca * xz + sa * y,
79 | yz - ca * yz - sa * x,
80 | resultzz + ca * (1.0 - zz),
81 | 0.0,
82 | 0, 0, 0, 1);
83 | }
84 |
85 | translation_rotation_scaling_matrix(t, r, s)
86 | = translation_matrix(t) * rotation_matrix(r) * scaling_matrix(s);
87 |
88 | transform(m, p: vector)
89 | = vector(
90 | p.x * m.m11 + p.y * m.m21, p.z * m.m31 + m.m41,
91 | p.x * m.m12 + p.y * m.m22, p.z * m.m32 + m.m42,
92 | p.x * m.m13 + p.y * m.m23, p.z * m.m33 + m.m43);
93 |
94 | op*(a: matrix, b: matrix)
95 | = matrix(
96 | a.m11 * b.m11 + a.m12 * b.m21 + a.m13 * b.m31 + a.m14 * b.m41,
97 | a.m11 * b.m12 + a.m12 * b.m22 + a.m13 * b.m32 + a.m14 * b.m42,
98 | a.m11 * b.m13 + a.m12 * b.m23 + a.m13 * b.m33 + a.m14 * b.m43,
99 | a.m11 * b.m14 + a.m12 * b.m24 + a.m13 * b.m34 + a.m14 * b.m44,
100 |
101 | a.m21 * b.m11 + a.m22 * b.m21 + a.m23 * b.m31 + a.m24 * b.m41,
102 | a.m21 * b.m12 + a.m22 * b.m22 + a.m23 * b.m32 + a.m24 * b.m42,
103 | a.m21 * b.m13 + a.m22 * b.m23 + a.m23 * b.m33 + a.m24 * b.m43,
104 | a.m21 * b.m14 + a.m22 * b.m24 + a.m23 * b.m34 + a.m24 * b.m44,
105 |
106 | a.m31 * b.m11 + a.m32 * b.m21 + a.m33 * b.m31 + a.m34 * b.m41,
107 | a.m31 * b.m12 + a.m32 * b.m22 + a.m33 * b.m32 + a.m34 * b.m42,
108 | a.m31 * b.m13 + a.m32 * b.m23 + a.m33 * b.m33 + a.m34 * b.m43,
109 | a.m31 * b.m14 + a.m32 * b.m24 + a.m33 * b.m34 + a.m34 * b.m44,
110 |
111 | a.m41 * b.m11 + a.m42 * b.m21 + a.m43 * b.m31 + a.m44 * b.m41,
112 | a.m41 * b.m12 + a.m42 * b.m22 + a.m43 * b.m32 + a.m44 * b.m42,
113 | a.m41 * b.m13 + a.m42 * b.m23 + a.m43 * b.m33 + a.m44 * b.m43,
114 | a.m41 * b.m14 + a.m42 * b.m24 + a.m43 * b.m34 + a.m44 * b.m44);
115 |
116 | function determinant(m)
117 | {
118 | var a = m.M11, b = m.M12, c = m.M13, d = m.M14;
119 | var e = m.M21, f = m.M22, g = m.M23, h = m.M24;
120 | var i = m.M31, j = m.M32, k = m.M33, l = m.M34;
121 | var m = m.M41, n = m.M42, o = m.M43, p = m.M44;
122 |
123 | var kp_lo = k * p - l * o;
124 | var jp_ln = j * p - l * n;
125 | var jo_kn = j * o - k * n;
126 | var ip_lm = i * p - l * m;
127 | var io_km = i * o - k * m;
128 | var in_jm = i * n - j * m;
129 |
130 | return a * (f * kp_lo - g * jp_ln + h * jo_kn) -
131 | b * (e * kp_lo - g * ip_lm + h * io_km) +
132 | c * (e * jp_ln - f * ip_lm + h * in_jm) -
133 | d * (e * jo_kn - f * io_km + g * in_jm);
134 |
135 | function invert(m)
136 | {
137 | var a = m.M11, b = m.M12, c = m.M13, d = m.M14;
138 | var e = m.M21, f = m.M22, g = m.M23, h = m.M24;
139 | var i = m.M31, j = m.M32, k = m.M33, l = m.M34;
140 | var m = m.M41, n = m.M42, o = m.M43, p = m.M44;
141 |
142 | var kp_lo = k * p - l * o;
143 | var jp_ln = j * p - l * n;
144 | var jo_kn = j * o - k * n;
145 | var ip_lm = i * p - l * m;
146 | var io_km = i * o - k * m;
147 | var in_jm = i * n - j * m;
148 |
149 | var a11 = +(f * kp_lo - g * jp_ln + h * jo_kn);
150 | var a12 = -(e * kp_lo - g * ip_lm + h * io_km);
151 | var a13 = +(e * jp_ln - f * ip_lm + h * in_jm);
152 | var a14 = -(e * jo_kn - f * io_km + g * in_jm);
153 |
154 | var det = a * a11 + b * a12 + c * a13 + d * a14;
155 |
156 | var invDet = 1.0f / det;
157 |
158 | var gp_ho = g * p - h * o;
159 | var fp_hn = f * p - h * n;
160 | var fo_gn = f * o - g * n;
161 | var ep_hm = e * p - h * m;
162 | var eo_gm = e * o - g * m;
163 | var en_fm = e * n - f * m;
164 |
165 | var gp_ho = g * p - h * o;
166 | var fp_hn = f * p - h * n;
167 | var fo_gn = f * o - g * n;
168 | var ep_hm = e * p - h * m;
169 | var eo_gm = e * o - g * m;
170 | var en_fm = e * n - f * m;
171 |
172 | var gl_hk = g * l - h * k;
173 | var fl_hj = f * l - h * j;
174 | var fk_gj = f * k - g * j;
175 | var el_hi = e * l - h * i;
176 | var ek_gi = e * k - g * i;
177 | var ej_fi = e * j - f * i;
178 |
179 | // TODO: rewrite. Note that these are four columns.
180 | // They could just be arrays.
181 |
182 | var col1 = [
183 | a11 * invDet,
184 | a12 * invDet,
185 | a13 * invDet,
186 | a14 * invDet];
187 |
188 | var col2 = [
189 | -(b * kp_lo - c * jp_ln + d * jo_kn) * invDet,
190 | +(a * kp_lo - c * ip_lm + d * io_km) * invDet,
191 | -(a * jp_ln - b * ip_lm + d * in_jm) * invDet,
192 | +(a * jo_kn - b * io_km + c * in_jm) * invDet]
193 |
194 | var col3 = [
195 | +(b * gp_ho - c * fp_hn + d * fo_gn) * invDet,
196 | -(a * gp_ho - c * ep_hm + d * eo_gm) * invDet,
197 | +(a * fp_hn - b * ep_hm + d * en_fm) * invDet,
198 | -(a * fo_gn - b * eo_gm + c * en_fm) * invDet];
199 |
200 | var col4 = [
201 | -(b * gl_hk - c * fl_hj + d * fk_gj) * invDet,
202 | +(a * gl_hk - c * el_hi + d * ek_gi) * invDet,
203 | -(a * fl_hj - b * el_hi + d * ej_fi) * invDet,
204 | +(a * fk_gj - b * ek_gi + c * ej_fi) * invDet];
205 |
206 | return matrix_from_cols(col1, col2, col3, col4);
207 | }
208 |
209 | matrix(q: quaternion)
210 | {
211 | var xx = q.X * q.X;
212 | var yy = q.Y * q.Y;
213 | var zz = q.Z * q.Z;
214 | var xy = q.X * q.Y;
215 | var wz = q.Z * q.W;
216 | var xz = q.Z * q.X;
217 | var wy = q.Y * q.W;
218 | var yz = q.Y * q.Z;
219 | var wx = q.X * q.W;
220 | return matrix(
221 | 1.0f - 2.0f * (yy + zz), 2.0f * (xy + wz), 2.0f * (xz - wy), 0.0f,
222 | 2.0f * (xy - wz), 1.0f - 2.0f * (zz + xx), 2.0f * (yz + wx), 0.0f,
223 | 2.0f * (xz + wy), 2.0f * (yz - wx), 1.0f - 2.0f * (yy + xx), 0.0f,
224 | 0.0f, 0.0f, 0.0f, 1.0f);
225 | }
226 |
227 | /* TODO: this might be a more efficient way to rotate a matrix.
228 | / Compute rotation matrix.
229 | float x2 = rotation.X + rotation.X;
230 | float y2 = rotation.Y + rotation.Y;
231 | float z2 = rotation.Z + rotation.Z;
232 |
233 | float wx2 = rotation.W * x2;
234 | float wy2 = rotation.W * y2;
235 | float wz2 = rotation.W * z2;
236 | float xx2 = rotation.X * x2;
237 | float xy2 = rotation.X * y2;
238 | float xz2 = rotation.X * z2;
239 | float yy2 = rotation.Y * y2;
240 | float yz2 = rotation.Y * z2;
241 | float zz2 = rotation.Z * z2;
242 |
243 | float q11 = 1.0f - yy2 - zz2;
244 | float q21 = xy2 - wz2;
245 | float q31 = xz2 + wy2;
246 |
247 | float q12 = xy2 + wz2;
248 | float q22 = 1.0f - xx2 - zz2;
249 | float q32 = yz2 - wx2;
250 |
251 | float q13 = xz2 - wy2;
252 | float q23 = yz2 + wx2;
253 | float q33 = 1.0f - xx2 - yy2;
254 |
255 | Matrix4x4 result;
256 |
257 | // First row
258 | result.M11 = value.M11 * q11 + value.M12 * q21 + value.M13 * q31;
259 | result.M12 = value.M11 * q12 + value.M12 * q22 + value.M13 * q32;
260 | result.M13 = value.M11 * q13 + value.M12 * q23 + value.M13 * q33;
261 | result.M14 = value.M14;
262 |
263 | // Second row
264 | result.M21 = value.M21 * q11 + value.M22 * q21 + value.M23 * q31;
265 | result.M22 = value.M21 * q12 + value.M22 * q22 + value.M23 * q32;
266 | result.M23 = value.M21 * q13 + value.M22 * q23 + value.M23 * q33;
267 | result.M24 = value.M24;
268 |
269 | // Third row
270 | result.M31 = value.M31 * q11 + value.M32 * q21 + value.M33 * q31;
271 | result.M32 = value.M31 * q12 + value.M32 * q22 + value.M33 * q32;
272 | result.M33 = value.M31 * q13 + value.M32 * q23 + value.M33 * q33;
273 | result.M34 = value.M34;
274 |
275 | // Fourth row
276 | result.M41 = value.M41 * q11 + value.M42 * q21 + value.M43 * q31;
277 | result.M42 = value.M41 * q12 + value.M42 * q22 + value.M43 * q32;
278 | result.M43 = value.M41 * q13 + value.M42 * q23 + value.M43 * q33;
279 | result.M44 = value.M44;
280 |
281 | return result;
282 | }
283 | */
284 | }
285 |
--------------------------------------------------------------------------------
/src/heron-to-js.ts:
--------------------------------------------------------------------------------
1 | import { FuncDef, FuncParamDef, VarDef } from "./heron-defs";
2 | import { normalizeType } from "./type-system";
3 | import { CodeBuilder } from "./code-builder";
4 | import { VarName, FunCall, ConditionalExpr, ObjectLiteral, ArrayLiteral, BoolLiteral, IntLiteral, FloatLiteral, StrLiteral, VarExpr, PostfixDec, Lambda, PostfixInc, Expr, ObjectField, VarAssignmentExpr } from "./heron-expr";
5 | import { Statement, CompoundStatement, IfStatement, EmptyStatement, VarDeclStatement, WhileStatement, DoStatement, ForStatement, ExprStatement, ContinueStatement, ReturnStatement } from "./heron-statement";
6 | import { Module } from "./heron-package";
7 | import { identifierToString } from "./heron-ast-rewrite";
8 | import { FuncRef } from "./heron-refs";
9 |
10 | let id = 0;
11 |
12 | export function toJavaScript(x: Statement|Expr|FuncDef|VarDef|Module): string {
13 | const toJs = new HeronToJs();
14 | toJs.visit(x);
15 | return toJs.cb.toString();
16 | }
17 |
18 | export function funcDefName(f: FuncDef) {
19 | return identifierToString(f.name) + '_' + f.node.id;
20 | }
21 |
22 | export function funcParamDefName(p: FuncParamDef) {
23 | return p.name;
24 | }
25 |
26 | // This class computes the type for a function
27 | export class HeronToJs
28 | {
29 | cb = new CodeBuilder();
30 |
31 | visit(x: Statement|Expr|FuncDef|VarDef|Module) {
32 | if (x instanceof Statement)
33 | this.visitStatement(x);
34 | else if (x instanceof Expr)
35 | this.visitExpr(x);
36 | else if (x instanceof FuncDef)
37 | this.visitFuncDef(x);
38 | else if (x instanceof VarDef)
39 | this.visitVarDef(x);
40 | else
41 | this.visitModule(x);
42 | }
43 |
44 | visitModule(m: Module) {
45 | this.cb.pushLine('// Module ' + m.name);
46 | this.cb.pushLine('// file ' + m.file.filePath);
47 | for (const i of m.imports)
48 | this.cb.pushLine('// imports ' + i);
49 | for (const v of m.vars)
50 | this.visit(v);
51 | for (const f of m.functions)
52 | this.visit(f);
53 | }
54 |
55 | functionSig(f: FuncDef): string {
56 | return 'function ' + funcDefName(f) + '(' + f.params.map(funcParamDefName).join(', ') + ')';
57 | }
58 |
59 | visitVarDef(v: VarDef) {
60 | // TODO: the 'v' has no type!
61 | this.cb.pushLine('// ' + normalizeType(v.type));
62 | this.cb.push("const " + v.name + ' = ');
63 | this.visit(v.exprNode.expr);
64 | this.cb.pushLine(';');
65 | }
66 |
67 | intrinsicName(f: FuncDef): string {
68 | let name = identifierToString(f.name);
69 | /*
70 | for (const p of f.params) {
71 | name += '_' + p.type.toString();
72 | }
73 | */
74 | return name;
75 | }
76 |
77 | visitFuncDef(f: FuncDef) {
78 | this.cb.pushLine('// ' + normalizeType(f.type));
79 | if (f.isIntrinsic) {
80 | this.cb.pushLine("const " + funcDefName(f) + ' = ' + this.intrinsicName(f) + ';');
81 | return;
82 | }
83 | this.cb.pushLine(this.functionSig(f));
84 | if (f.body.statement)
85 | {
86 | this.visit(f.body.statement);
87 | }
88 | else if (f.body.expr) {
89 | this.cb.pushLine('{');
90 | this.cb.push('return ');
91 | this.visit(f.body.expr);
92 | this.cb.pushLine(';');
93 | this.cb.pushLine('}');
94 | }
95 | else
96 | throw new Error("No body statement or expression");
97 | }
98 |
99 | visitStatement(statement: Statement)
100 | {
101 | if (statement instanceof CompoundStatement)
102 | {
103 | if (statement.statements.length === 0)
104 | this.cb.pushLine('{ }');
105 | else
106 | {
107 | this.cb.pushLine('{');
108 | for (const st of statement.statements)
109 | this.visit(st);
110 | this.cb.pushLine('}');
111 | }
112 | }
113 | else if (statement instanceof IfStatement) {
114 | this.cb.push('if (')
115 | this.visit(statement.condition);
116 | this.cb.pushLine(')');
117 | this.visit(statement.onTrue);
118 | this.cb.pushLine('else');
119 | this.visit(statement.onFalse);
120 | }
121 | else if (statement instanceof ReturnStatement) {
122 | this.cb.push('return ');
123 | if (statement.expr)
124 | this.visit(statement.expr);
125 | this.cb.pushLine(';');
126 | }
127 | else if (statement instanceof ContinueStatement) {
128 | this.cb.pushLine('continue;')
129 | }
130 | else if (statement instanceof ExprStatement) {
131 | this.visit(statement.expr);
132 | this.cb.pushLine(';')
133 | }
134 | else if (statement instanceof ForStatement) {
135 | const x = "i" + id++;
136 | this.cb.push(`for (let ${x}=0; ${x} < `);
137 | this.visit(statement.array);
138 | this.cb.pushLine(`.length; ++${x})`);
139 | this.cb.pushLine('{');
140 | this.cb.push('const ');
141 | this.cb.push(statement.identifier);
142 | this.cb.push(' = ');
143 | this.visit(statement.array);
144 | this.cb.pushLine(`[${x}];`);
145 | this.visit(statement.loop);
146 | this.cb.pushLine('}');
147 | }
148 | else if (statement instanceof DoStatement) {
149 | this.cb.pushLine('do');
150 | this.visit(statement.body);
151 | this.cb.push('while (');
152 | this.visit(statement.condition);
153 | this.cb.pushLine(')');
154 | }
155 | else if (statement instanceof WhileStatement) {
156 | this.cb.push('while (');
157 | this.visit(statement.condition);
158 | this.cb.pushLine(')');
159 | this.visit(statement.body);
160 | }
161 | else if (statement instanceof VarDeclStatement) {
162 | for (const vd of statement.vars) {
163 | // TODO: I want to know when I can use 'const' instead of 'let'
164 | this.cb.push('let ' + vd.name + ' = ');
165 | this.visit(vd.exprNode.expr);
166 | this.cb.pushLine(';');
167 | }
168 | }
169 | else if (statement instanceof EmptyStatement) {
170 | // Do nothing.
171 | }
172 | else {
173 | throw new Error("Unrecognized statement " + statement);
174 | }
175 | }
176 |
177 | visitDelimited(xs: Expr[], delim = ',') {
178 | for (let i=0; i < xs.length; ++i) {
179 | if (i > 0) this.cb.push(delim);
180 | this.visit(xs[i])
181 | }
182 | }
183 |
184 | visitExpr(expr: Expr, noType = false)
185 | {
186 | if (expr instanceof VarName) {
187 | if (expr.node.ref instanceof FuncRef)
188 | {
189 | if (expr.node.ref.defs.length === 1) {
190 | this.cb.push(funcDefName(expr.node.ref.defs[0]));
191 | }
192 | else {
193 | // TODO: if I could find a better way to compute this, so it was nearly impossible to screw up,
194 | // that would be great.
195 | if (expr.functionIndex === -1) {
196 | throw new Error("No function index was computed for expression: " + expr);
197 | }
198 | else {
199 | this.cb.push(funcDefName(expr.node.ref.defs[expr.functionIndex]));
200 | }
201 | }
202 | }
203 | else
204 | {
205 | this.cb.push(identifierToString(expr.name));
206 | }
207 | }
208 | else if (expr instanceof FunCall) {
209 | // TODO: this will fail when used with a lambda in the calling position.
210 | const fd = expr.func.node.ref.defs[expr.functionIndex];
211 | if (fd instanceof FuncDef)
212 | {
213 | // If this is a known reference to a funcion
214 | this.cb.push(funcDefName(fd));
215 | }
216 | else
217 | {
218 | // We just visit it like an ordinary function
219 | this.visit(expr.func);
220 | }
221 | this.cb.push('(');
222 | this.visitDelimited(expr.args);
223 | this.cb.push(')');
224 | }
225 | else if (expr instanceof ConditionalExpr) {
226 | this.visit(expr.condition);
227 | this.cb.push(' ? ');
228 | this.visit(expr.onTrue);
229 | this.cb.push(' : ');
230 | this.visit(expr.onFalse);
231 | }
232 | else if (expr instanceof ObjectLiteral || expr instanceof ObjectField) {
233 | throw new Error("Object literals not yet supported");
234 | }
235 | else if (expr instanceof ArrayLiteral) {
236 | this.cb.push('[');
237 | this.visitDelimited(expr.vals);
238 | this.cb.push(']');
239 | }
240 | else if (expr instanceof BoolLiteral) {
241 | this.cb.push(expr.value ? "true" : "false");
242 | }
243 | else if (expr instanceof IntLiteral) {
244 | this.cb.push(expr.value.toString());
245 | }
246 | else if (expr instanceof FloatLiteral) {
247 | this.cb.push(expr.value.toString());
248 | }
249 | else if (expr instanceof StrLiteral) {
250 | // TODO: escape the special charaters in value
251 | this.cb.push('"' + expr.value + '"');
252 | }
253 | else if (expr instanceof VarExpr) {
254 | let vars = expr.vars.slice();
255 | for (let i=0; i ');
260 | }
261 | this.visit(expr.expr);
262 | for (let i=vars.length-1; i >= 0; --i) {
263 | const vd=vars[i];
264 | this.cb.push(')(');
265 | this.visit(vd.exprNode.expr);
266 | this.cb.push(')');
267 | }
268 | this.cb.pushLine();
269 | }
270 | else if (expr instanceof Lambda) {
271 | this.cb.push('(')
272 | this.cb.push(expr.params.map(p => p.name).join(', '));
273 | this.cb.push(') => ')
274 | if (expr.bodyNode.expr)
275 | this.visit(expr.bodyNode.expr);
276 | else
277 | this.visit(expr.bodyNode.statement);
278 | }
279 | else if (expr instanceof PostfixDec) {
280 | this.visitExpr(expr.lvalue, true);
281 | this.cb.push('--');
282 | }
283 | else if (expr instanceof PostfixInc) {
284 | this.visitExpr(expr.lvalue, true);
285 | this.cb.push('++');
286 | }
287 | else if (expr instanceof VarAssignmentExpr) {
288 | this.cb.push(expr.name + ' = ');
289 | this.visit(expr.value);
290 | }
291 | else {
292 | throw new Error("Not a recognized expression " + expr);
293 | }
294 |
295 | if (expr.type && !noType) {
296 | this.cb.pushLine(' // ' + expr.type);
297 | }
298 | }
299 | }
--------------------------------------------------------------------------------
/src/heron-to-glsl.ts:
--------------------------------------------------------------------------------
1 | import { FuncDef, FuncParamDef, VarDef } from "./heron-defs";
2 | import { normalizeType, PolyType } from "./type-system";
3 | import { CodeBuilder } from "./code-builder";
4 | import { VarName, FunCall, ConditionalExpr, ObjectLiteral, ArrayLiteral, BoolLiteral, IntLiteral, FloatLiteral, StrLiteral, VarExpr, PostfixDec, Lambda, PostfixInc, Expr, ObjectField, VarAssignmentExpr } from "./heron-expr";
5 | import { Statement, CompoundStatement, IfStatement, EmptyStatement, VarDeclStatement, WhileStatement, DoStatement, ForStatement, ExprStatement, ContinueStatement, ReturnStatement } from "./heron-statement";
6 | import { Module } from "./heron-package";
7 | import { identifierToString } from "./heron-ast-rewrite";
8 | import { FuncRef } from "./heron-refs";
9 | import { getFuncReturnType, HeronType } from "./heron-types";
10 | import { toHeronTypeString } from "./heron-to-html";
11 |
12 | // https://www.khronos.org/opengl/wiki/Core_Language_(GLSL)
13 | // https://www.khronos.org/opengl/wiki/Data_Type_(GLSL)
14 | // http://www.shaderific.com/glsl-statements
15 |
16 | let id = 0;
17 |
18 | export function toGlsl(x: Statement|Expr|FuncDef|VarDef|Module): string {
19 | const toGlsl = new HeronToGlsl();
20 | toGlsl.visit(x);
21 | return toGlsl.cb.toString();
22 | }
23 |
24 | export function funcDefName(f: FuncDef) {
25 | return identifierToString(f.name) + '_' + f.node.id;
26 | }
27 |
28 | function funcParamString(p: FuncParamDef) {
29 | return toTypeString(p.type) + " " + p.name;
30 | }
31 |
32 | function toTypeString(t: HeronType): string {
33 | switch (t.toString())
34 | {
35 | case "":
36 | default:
37 | return "???"
38 | }
39 | }
40 |
41 | // This class computes the type for a function
42 | export class HeronToGlsl
43 | {
44 | cb = new CodeBuilder();
45 |
46 | visit(x: Statement|Expr|FuncDef|VarDef|Module) {
47 | if (x instanceof Statement)
48 | this.visitStatement(x);
49 | else if (x instanceof Expr)
50 | this.visitExpr(x);
51 | else if (x instanceof FuncDef)
52 | this.visitFuncDef(x);
53 | else if (x instanceof VarDef)
54 | this.visitVarDef(x);
55 | else
56 | this.visitModule(x);
57 | }
58 |
59 | visitModule(m: Module) {
60 | this.cb.pushLine('// Module ' + m.name);
61 | this.cb.pushLine('// file ' + m.file.filePath);
62 | for (const i of m.imports)
63 | this.cb.pushLine('// imports ' + i);
64 | for (const v of m.vars)
65 | this.visit(v);
66 | for (const f of m.functions)
67 | this.visit(f);
68 | }
69 |
70 | functionSig(f: FuncDef): string {
71 | const ret = toTypeString(getFuncReturnType(f.type as PolyType));
72 | return ret + ' ' + funcDefName(f) + '(' + f.params.map(funcParamString).join(', ') + ')';
73 | }
74 |
75 | visitVarDef(v: VarDef) {
76 | this.cb.push(toTypeString(v.type) + " " + v.name + ' = ');
77 | this.visit(v.exprNode.expr);
78 | this.cb.pushLine(';');
79 | }
80 |
81 | intrinsicName(f: FuncDef): string {
82 | let name = identifierToString(f.name);
83 | /*
84 | for (const p of f.params) {
85 | name += '_' + p.type.toString();
86 | }
87 | */
88 | return name;
89 | }
90 |
91 | visitFuncDef(f: FuncDef) {
92 | if (f.isIntrinsic)
93 | return;
94 |
95 | this.cb.pushLine(this.functionSig(f));
96 | if (f.body.statement)
97 | {
98 | this.visit(f.body.statement);
99 | }
100 | else if (f.body.expr) {
101 | this.cb.pushLine('{');
102 | this.cb.push('return ');
103 | this.visit(f.body.expr);
104 | this.cb.pushLine(';');
105 | this.cb.pushLine('}');
106 | }
107 | else
108 | throw new Error("No body statement or expression");
109 | }
110 |
111 | visitStatement(statement: Statement)
112 | {
113 | if (statement instanceof CompoundStatement)
114 | {
115 | if (statement.statements.length === 0)
116 | this.cb.pushLine('{ }');
117 | else
118 | {
119 | this.cb.pushLine('{');
120 | for (const st of statement.statements)
121 | this.visit(st);
122 | this.cb.pushLine('}');
123 | }
124 | }
125 | else if (statement instanceof IfStatement) {
126 | this.cb.push('if (')
127 | this.visit(statement.condition);
128 | this.cb.pushLine(')');
129 | this.visit(statement.onTrue);
130 | this.cb.pushLine('else');
131 | this.visit(statement.onFalse);
132 | }
133 | else if (statement instanceof ReturnStatement) {
134 | this.cb.push('return ');
135 | if (statement.expr)
136 | this.visit(statement.expr);
137 | this.cb.pushLine(';');
138 | }
139 | else if (statement instanceof ContinueStatement) {
140 | this.cb.pushLine('continue;')
141 | }
142 | else if (statement instanceof ExprStatement) {
143 | this.visit(statement.expr);
144 | this.cb.pushLine(';')
145 | }
146 | else if (statement instanceof ForStatement) {
147 | const i = "i" + id++;
148 | const t = toTypeString(statement.type);
149 | const e = statement.identifier;
150 | const n = "n" + id++;
151 | this.cb.push(`${n} = `)
152 | this.visit(statement.array);
153 | this.cb.pushLine(".length;");
154 | this.cb.push(`for (int ${i}=0; ${i} < ${n}; ++${i})`);
155 | this.cb.pushLine('{');
156 | this.cb.push(`${t} ${e} = `);
157 | this.visit(statement.array);
158 | this.cb.pushLine(`[${i}];`);
159 | this.visit(statement.loop);
160 | this.cb.pushLine('}');
161 | }
162 | else if (statement instanceof DoStatement) {
163 | this.cb.pushLine('do');
164 | this.visit(statement.body);
165 | this.cb.push('while (');
166 | this.visit(statement.condition);
167 | this.cb.pushLine(')');
168 | }
169 | else if (statement instanceof WhileStatement) {
170 | this.cb.push('while (');
171 | this.visit(statement.condition);
172 | this.cb.pushLine(')');
173 | this.visit(statement.body);
174 | }
175 | else if (statement instanceof VarDeclStatement) {
176 | for (const vd of statement.vars) {
177 | const t = toHeronTypeString(vd.type);
178 | this.cb.push(t + ' ' + vd.name + ' = ');
179 | this.visit(vd.exprNode.expr);
180 | this.cb.pushLine(';');
181 | }
182 | }
183 | else if (statement instanceof EmptyStatement) {
184 | // Do nothing.
185 | }
186 | else {
187 | throw new Error("Unrecognized statement " + statement);
188 | }
189 | }
190 |
191 | visitDelimited(xs: Expr[], delim = ',') {
192 | for (let i=0; i < xs.length; ++i) {
193 | if (i > 0) this.cb.push(delim);
194 | this.visit(xs[i])
195 | }
196 | }
197 |
198 | visitExpr(expr: Expr, noType = false)
199 | {
200 | if (expr instanceof VarName) {
201 | if (expr.node.ref instanceof FuncRef)
202 | {
203 | if (expr.node.ref.defs.length === 1) {
204 | this.cb.push(funcDefName(expr.node.ref.defs[0]));
205 | }
206 | else {
207 | // TODO: if I could find a better way to compute this, so it was nearly impossible to screw up,
208 | // that would be great.
209 | if (expr.functionIndex === -1) {
210 | throw new Error("No function index was computed for expression: " + expr);
211 | }
212 | else {
213 | this.cb.push(funcDefName(expr.node.ref.defs[expr.functionIndex]));
214 | }
215 | }
216 | }
217 | else
218 | {
219 | this.cb.push(identifierToString(expr.name));
220 | }
221 | }
222 | else if (expr instanceof FunCall) {
223 | // TODO: this will fail when used with a lambda in the calling position.
224 | const fd = expr.func.node.ref.defs[expr.functionIndex];
225 | if (fd instanceof FuncDef)
226 | {
227 | // If this is a known reference to a funcion
228 | this.cb.push(funcDefName(fd));
229 | }
230 | else
231 | {
232 | // We just visit it like an ordinary function
233 | this.visit(expr.func);
234 | }
235 | this.cb.push('(');
236 | this.visitDelimited(expr.args);
237 | this.cb.push(')');
238 | }
239 | else if (expr instanceof ConditionalExpr) {
240 | this.visit(expr.condition);
241 | this.cb.push(' ? ');
242 | this.visit(expr.onTrue);
243 | this.cb.push(' : ');
244 | this.visit(expr.onFalse);
245 | }
246 | else if (expr instanceof ObjectLiteral || expr instanceof ObjectField) {
247 | throw new Error("Object literals not yet supported");
248 | }
249 | else if (expr instanceof ArrayLiteral) {
250 | this.cb.push('[');
251 | this.visitDelimited(expr.vals);
252 | this.cb.push(']');
253 | }
254 | else if (expr instanceof BoolLiteral) {
255 | this.cb.push(expr.value ? "true" : "false");
256 | }
257 | else if (expr instanceof IntLiteral) {
258 | this.cb.push(expr.value.toString());
259 | }
260 | else if (expr instanceof FloatLiteral) {
261 | this.cb.push(expr.value.toString());
262 | }
263 | else if (expr instanceof StrLiteral) {
264 | // TODO: escape the special charaters in value
265 | this.cb.push('"' + expr.value + '"');
266 | }
267 | else if (expr instanceof VarExpr) {
268 | let vars = expr.vars.slice();
269 | for (let i=0; i ');
274 | }
275 | this.visit(expr.expr);
276 | for (let i=vars.length-1; i >= 0; --i) {
277 | const vd=vars[i];
278 | this.cb.push(')(');
279 | this.visit(vd.exprNode.expr);
280 | this.cb.push(')');
281 | }
282 | this.cb.pushLine();
283 | }
284 | else if (expr instanceof Lambda) {
285 | this.cb.push('(')
286 | this.cb.push(expr.params.map(p => p.name).join(', '));
287 | this.cb.push(') => ')
288 | if (expr.bodyNode.expr)
289 | this.visit(expr.bodyNode.expr);
290 | else
291 | this.visit(expr.bodyNode.statement);
292 | }
293 | else if (expr instanceof PostfixDec) {
294 | this.visitExpr(expr.lvalue, true);
295 | this.cb.push('--');
296 | }
297 | else if (expr instanceof PostfixInc) {
298 | this.visitExpr(expr.lvalue, true);
299 | this.cb.push('++');
300 | }
301 | else if (expr instanceof VarAssignmentExpr) {
302 | this.cb.push(expr.name + ' = ');
303 | this.visit(expr.value);
304 | }
305 | else {
306 | throw new Error("Not a recognized expression " + expr);
307 | }
308 |
309 | if (expr.type && !noType) {
310 | this.cb.pushLine(' // ' + expr.type);
311 | }
312 | }
313 | }
--------------------------------------------------------------------------------
/src/heron-package.ts:
--------------------------------------------------------------------------------
1 | import { HeronAstNode, validateNode, throwError, preprocessAst, visitAst } from "./heron-ast-rewrite";
2 | import { Def, createDef, VarDef, FuncDef, TypeDef, TypeParamDef, FuncParamDef, ForLoopVarDef } from "./heron-defs";
3 | import { Ref, FuncRef, TypeRef, TypeParamRef, FuncParamRef, VarRef, ForLoopVarRef } from "./heron-refs";
4 | import { computeExpr } from "./heron-expr";
5 | import { NameAnalyzer, Scope } from "./heron-name-analysis";
6 | import { createStatement, VarDeclStatement } from "./heron-statement";
7 |
8 | // A package is a compiled system. It contains a set of modules in different source files.
9 | export class Package
10 | {
11 | modules: Module[] = [];
12 | scope: Scope = new Scope(null);
13 | scopes: Scope[] = [this.scope];
14 | files: SourceFile[] = [];
15 |
16 | get defs(): Def[] {
17 | return this.scope.allDefs();
18 | }
19 |
20 | get refs(): Ref[] {
21 | return this.scope.allRefs();
22 | }
23 |
24 | findFunction(name: string): FuncDef{
25 | for (const m of this.modules)
26 | for (const f of m.functions)
27 | if (f.name === name)
28 | return f;
29 | }
30 |
31 | get allFuncDefs(): FuncDef[] {
32 | let r: FuncDef[] = [];
33 | for (let m of this.modules)
34 | for (let c of m.body.children)
35 | if (c.def instanceof FuncDef)
36 | r.push(c.def);
37 | r.sort((d1, d2) => (d1.name < d2.name) ? -1 : (d1.name > d2.name ? 1 : 0));
38 | return r;
39 | }
40 |
41 | get allVarDefs(): VarDef[] {
42 | let r: VarDef[] = [];
43 | for (let m of this.modules)
44 | for (let c of m.body.children)
45 | // TODO: I hate that var decl statements are compound. We should really really rewrite them.
46 | if (c.statement instanceof VarDeclStatement)
47 | for (const vd of c.statement.vars)
48 | r.push(vd);
49 | r.sort((d1, d2) => (d1.name < d2.name) ? -1 : (d1.name > d2.name ? 1 : 0));
50 | return r;
51 | }
52 |
53 | // When done adding files call "processModules" to sort the dependencies
54 | addFile(node: HeronAstNode, intrinsic: boolean, filePath: string) {
55 | validateNode(node, 'file');
56 | let langVerNode = validateNode(node.children[0], 'langVer');
57 | //let langVer = this.parseURN(langVerNode);
58 | //if (langVer.length != 3) throwError(langVerNode, "Expected three component to language version URN: name, flavor, and version")
59 | let file = new SourceFile(node, intrinsic, filePath, langVerNode.allText);
60 | this.files.push(file);
61 |
62 | let moduleNode = validateNode(node.children[1], 'module');
63 | let moduleNameNode = validateNode(moduleNode.children[0], 'moduleName');
64 | let moduleBodyNode = validateNode(moduleNode.children[1], 'moduleBody');
65 | let moduleNameURN = this.parseModuleName(moduleNameNode);
66 | let importNodes = moduleBodyNode.children.filter(c => c.name === 'importStatement');
67 | let importURNs = importNodes.map(n => this.parseModuleName(n.children[0]));
68 | let module = new Module(moduleNode, moduleNameURN, file, importURNs, moduleBodyNode);
69 | this.modules.push(module);
70 | }
71 |
72 | // Given a URN
73 | parseURN(node: HeronAstNode): string[] {
74 | return node.children.map(c => c.allText);
75 | }
76 |
77 | // Extract the URN for a module name from the node
78 | parseModuleName(node: HeronAstNode): string {
79 | validateNode(node, 'moduleName');
80 | return node.allText;
81 | }
82 |
83 | // Load the definitions of a modules
84 | loadModuleDefs(module: Module) {
85 | let moduleBody = validateNode(module.node.children[1], 'moduleBody');
86 | for (let c of moduleBody.children)
87 | if (c.def) {
88 | // Func def or type def
89 | this.addDef(c.def);
90 | }
91 | else if (c.statement instanceof VarDeclStatement) {
92 | // If a variable declaration there are potentially multiple variable defs
93 | for (const vd of c.statement.vars)
94 | this.addDef(vd);
95 | }
96 |
97 | }
98 |
99 | // Load the definitions from the various dependent modules
100 | loadModuleDependencies(module: Module) {
101 | for (let imp of module.imports) {
102 | let impMod = this.getModule(imp);
103 | if (!impMod)
104 | throwError(module.node, "Could not find imported module " + imp);
105 | this.loadModuleDefs(impMod);
106 | }
107 | }
108 |
109 | // Called once all of the files have been added.
110 | processModules()
111 | {
112 | // Order modules, so that dependencies are resolved correctly.
113 | // A module cannot have a cyclical dependency
114 | this.sortModuleDependencies();
115 |
116 | // The visitor will be used for adding scopes and references
117 | let nameAnalyzer = new NameAnalyzer();
118 |
119 | // Iterate over the modules, pre-process their trees and create definitions.
120 | for (let m of this.modules)
121 | {
122 | let ast = m.node;
123 |
124 | // Perform pre-processing
125 | preprocessAst(ast, m.file);
126 |
127 | // Create definitions
128 | visitAst(ast, createDef);
129 |
130 | // Create expressions, and add them to the nodes
131 | visitAst(ast, computeExpr);
132 |
133 | // Create statement, and add them to the nodes
134 | visitAst(ast, createStatement);
135 |
136 | // Rewrite the if statements
137 | //visitAst(ast, rewriteIfStatements);
138 | }
139 |
140 | // Analyze names for each module.
141 | for (let m of this.modules)
142 | {
143 | this.pushScope(m.node);
144 | // Firt load all intrinsic definitions into the global scope
145 | for (let m of this.modules)
146 | if (m.file.intrinsic)
147 | this.loadModuleDefs(m);
148 | this.loadModuleDependencies(m);
149 | this.loadModuleDefs(m);
150 | nameAnalyzer.visitNode(m.node, this);
151 | this.popScope();
152 | }
153 | }
154 |
155 | // Resolves links and assures that the language
156 | sortModuleDependencies() {
157 | let queue=[...this.modules];
158 | let result: Module[] =[];
159 |
160 | let tmp = [];
161 | for (let m of queue)
162 | {
163 | if (m.file.intrinsic)
164 | {
165 | if (m.imports.length > 0)
166 | throwError(m.node, "Intrinsic modules should not have imports");
167 | result.push(m);
168 | }
169 | else
170 | {
171 | tmp.push(m);
172 | }
173 | }
174 | queue = tmp;
175 |
176 | // Local function (recursive)
177 | function process(next: string) {
178 | // Check if already processed
179 | if (result.some(m => m.name === next))
180 | return;
181 |
182 | // Look for the module in the queue
183 | let i=queue.length;
184 | while (--i >= 0)
185 | if (queue[i].name === next)
186 | break;
187 | if (i < 0) throw new Error("Could not find module named: " + next);
188 | let m=queue[i];
189 |
190 | // First process imported modules
191 | for (let importName of m.imports)
192 | process(importName);
193 |
194 | // Remove from queue
195 | queue.splice(i, 1);
196 |
197 | // Add to the result
198 | result.push(m);
199 | }
200 |
201 | while (queue.length > 0)
202 | process(queue[0].name);
203 |
204 | // We have no sorted the modules according to their dependencies.
205 | this.modules = result;
206 | }
207 |
208 | // Scans for modules
209 | getModule(name: string) {
210 | for (let m of this.modules)
211 | if (m.name === name)
212 | return m;
213 | throw new Error("Module not found " + name);
214 | }
215 |
216 | //=============================================
217 | // These functions are used by the visitor to incrementally build the package
218 |
219 | pushScope(node: HeronAstNode): Scope {
220 | let scope = new Scope(node);
221 | scope.id = this.scopes.length;
222 | this.scopes.push(scope);
223 | this.scope.children.push(scope);
224 | scope.parent = this.scope;
225 | return this.scope = scope;
226 | }
227 |
228 | popScope() {
229 | this.scope = this.scope.parent;
230 | }
231 |
232 | findDefs(name: string): Def[] {
233 | return this.scope.findDefs(name);
234 | }
235 |
236 | addDef(def: Def) {
237 | if (def && this.scope.defs.indexOf(def) < 0)
238 | this.scope.defs.push(def);
239 | }
240 |
241 | addRef(name: string, node: HeronAstNode): Ref {
242 | let defs = this.findDefs(name);
243 | if (defs.length === 0)
244 | throwError(node, "Could not find defintiions for " + name);
245 | let ref: Ref;
246 | if (defs[0] instanceof FuncDef) {
247 | if (!defs.every(d => d instanceof FuncDef))
248 | throwError(node, "Reference resolved to mix of function definitions and variable definitions: " + name);
249 | ref = new FuncRef(node, name, this.scope, defs as FuncDef[]);
250 | }
251 | else {
252 | if (defs.length !== 1)
253 | throwError(node, "Reference resolved to multiple variable definitions: " + name);
254 | let def = defs[0];
255 | if (def instanceof TypeDef) {
256 | ref = new TypeRef(node, name, this.scope, def);
257 | }
258 | else if (def instanceof TypeParamDef) {
259 | ref = new TypeParamRef(node, name, this.scope, def);
260 | }
261 | else if (def instanceof FuncParamDef) {
262 | ref = new FuncParamRef(node, name, this.scope, def);
263 | }
264 | else if (def instanceof VarDef) {
265 | ref = new VarRef(node, name, this.scope, def);
266 | }
267 | else if (def instanceof ForLoopVarDef) {
268 | ref = new ForLoopVarRef(node, name, this.scope, def);
269 | }
270 | else {
271 | throwError(node, "Unrecognized definition type " + def);
272 | }
273 | }
274 |
275 | this.scope.refs.push(ref);
276 | return ref;
277 | }
278 | }
279 |
280 | // Wraps a source file
281 | export class SourceFile
282 | {
283 | constructor(
284 | public readonly node: HeronAstNode,
285 | public readonly intrinsic: boolean,
286 | public readonly filePath: string,
287 | public readonly lang: string,
288 | )
289 | { node.file = this; }
290 | }
291 |
292 | // Represents the definition of a module
293 | export class Module
294 | {
295 | constructor(
296 | public readonly node: HeronAstNode,
297 | public readonly name: string,
298 | public readonly file: SourceFile,
299 | public readonly imports: string[],
300 | public readonly body: HeronAstNode,
301 | )
302 | { }
303 |
304 | get functions(): FuncDef[] {
305 | const r: FuncDef[] = [];
306 | for (const x of this.body.children.map(c => c.def))
307 | if (x && x instanceof FuncDef)
308 | r.push(x);
309 | return r;
310 | }
311 |
312 | get vars(): VarDef[] {
313 | const r: VarDef[] = [];
314 | for (const x of this.body.children)
315 | if (x.statement instanceof VarDeclStatement)
316 | for (const vd of x.statement.vars)
317 | r.push(vd);
318 | return r;
319 | }
320 | }
321 |
--------------------------------------------------------------------------------
/src/heron-to-text.ts:
--------------------------------------------------------------------------------
1 | import { CodeBuilder } from "./code-builder";
2 | import { HeronAstNode } from "./heron-ast-rewrite";
3 |
4 | // Given an AST, will generate a text representation of the code as Heron code
5 | // that has been marked up with the analysis results.
6 | export function heronToText(ast: HeronAstNode): string {
7 | let v = new HeronToTextVisitor();
8 | let cb = new CodeBuilder();
9 | let now = new Date();
10 | cb.pushLine('// Generated on ' + now.toDateString() + ' ' + now.toTimeString());
11 | v.visitNode(ast, cb);
12 | return cb.lines.join('');
13 | }
14 |
15 | function outputDetails(node: HeronAstNode, state: CodeBuilder) {
16 | /*
17 | if (node.scope)
18 | state.pushLine('// scope ' + node.scope);
19 | if (node.ref)
20 | state.pushLine('// reference ' + node.ref);
21 | if (node.def)
22 | state.pushLine('// definition ' + node.def);
23 | if (node.expr)
24 | state.pushLine('// expression ' + node.expr.constructor['name'] + ' ' + node.expr);
25 | if (node.type)
26 | state.pushLine('// type ' + node.type);
27 | */
28 | if (node.expr)
29 | state.pushLine('// expression type ' + node.expr.type);
30 | if (node.def)
31 | state.pushLine('// definition type ' + node.def.type);
32 | }
33 |
34 | // A visitor class for generating Heron code.
35 | class HeronToTextVisitor
36 | {
37 | visitNode(ast: HeronAstNode, state: CodeBuilder) {
38 | outputDetails(ast, state);
39 | const fnName = 'visit_' + ast.name;
40 | if (fnName in this)
41 | this[fnName](ast, state);
42 | else
43 | this.visitChildren(ast, state);
44 | }
45 | visitChildren(ast: HeronAstNode, state: CodeBuilder) {
46 | for (let child of ast.children)
47 | this.visitNode(child, state);
48 | }
49 | visitChildrenDelimited(ast, state, delim) {
50 | for (let i=0; i < ast.children.length; ++i) {
51 | if (i > 0)
52 | state.push(delim);
53 | this.visitNode(ast.children[i], state);
54 | }
55 | }
56 | visit_additiveOp(ast: HeronAstNode, state: CodeBuilder) {
57 | state.push(" " + ast.allText + " ");
58 | }
59 | visit_arrayExpr(ast: HeronAstNode, state: CodeBuilder) {
60 | state.push('[');
61 | this.visitChildrenDelimited(ast, state, ", ");
62 | state.push(']');
63 | }
64 | visit_arrayIndex(ast: HeronAstNode, state: CodeBuilder) {
65 | state.push('[');
66 | this.visitChildren(ast, state);
67 | state.push(']');
68 | }
69 | visit_assignmentOp(ast: HeronAstNode, state: CodeBuilder) {
70 | state.push(" " + ast.allText + " ")
71 | }
72 | visit_bool(ast: HeronAstNode, state: CodeBuilder) {
73 | state.push(ast.allText);
74 | }
75 | visit_breakStatement(ast: HeronAstNode, state: CodeBuilder) {
76 | state.pushLine("break;")
77 | }
78 | visit_compoundStatement(ast: HeronAstNode, state: CodeBuilder) { // recStatement[0,Infinity]
79 | state.pushLine("{");
80 | this.visitChildren(ast, state);
81 | state.pushLine("}");
82 | }
83 | visit_conditionalExprRight(ast: HeronAstNode, state: CodeBuilder) {
84 | state.push(' ? ');
85 | this.visitNode(ast.children[0], state);
86 | this.visitNode(ast.children[1], state);
87 | }
88 | visit_continueStatement(ast: HeronAstNode, state: CodeBuilder) {
89 | state.push('continue;');
90 | }
91 | visit_doLoop(ast: HeronAstNode, state: CodeBuilder) {
92 | state.pushLine('do');
93 | this.visitChildren(ast, state);
94 | }
95 | visit_doubleQuotedStringContents(ast: HeronAstNode, state: CodeBuilder) {
96 | state.push('"');
97 | state.push(ast.allText);
98 | state.push('"');
99 | }
100 | visit_elseStatement(ast: HeronAstNode, state: CodeBuilder) {
101 | state.pushLine('else');
102 | this.visitChildren(ast, state);
103 | }
104 | visit_emptyStatement(ast: HeronAstNode, state: CodeBuilder) {
105 | state.pushLine(';');
106 | }
107 | visit_equalityOp(ast: HeronAstNode, state: CodeBuilder) {
108 | state.push(' ' + ast.allText + ' ');
109 | }
110 | visit_exprStatement(ast: HeronAstNode, state: CodeBuilder) {
111 | this.visitChildren(ast, state);
112 | state.pushLine(';');
113 | }
114 | visit_fieldSelect(ast: HeronAstNode, state: CodeBuilder) {
115 | state.push('.');
116 | this.visitChildren(ast, state);
117 | }
118 | visit_forLoop(ast: HeronAstNode, state: CodeBuilder) {
119 | state.push('for (');
120 | this.visitNode(ast.children[0], state);
121 | state.push(' in ');
122 | this.visitNode(ast.children[1], state);
123 | state.pushLine(')');
124 | this.visitNode(ast.children[2], state);
125 | }
126 | visit_funCall(ast: HeronAstNode, state: CodeBuilder) {
127 | state.push('(');
128 | this.visitChildrenDelimited(ast, state, ', ');
129 | state.push(')');
130 | }
131 | visit_funcDef(ast: HeronAstNode, state: CodeBuilder) {
132 | state.pushLine('');
133 | state.pushLine('/**');
134 | state.pushLine(ast.allText);
135 | state.pushLine('*/');
136 | outputDetails(ast, state);
137 | state.push('function ');
138 | this.visitChildren(ast, state);
139 | }
140 | visit_funcName(ast: HeronAstNode, state: CodeBuilder) {
141 | state.push(ast.allText);
142 | }
143 | visit_funcParam(ast: HeronAstNode, state: CodeBuilder) {
144 | this.visitNode(ast.children[0], state);
145 | if (ast.children.length > 1) {
146 | state.push(' : ');
147 | this.visitNode(ast.children[1], state);
148 | }
149 | }
150 | visit_funcParamName(ast: HeronAstNode, state: CodeBuilder) {
151 | state.push(ast.allText);
152 | }
153 | visit_funcParams(ast: HeronAstNode, state: CodeBuilder) {
154 | state.push('(');
155 | this.visitChildrenDelimited(ast, state, ", ");
156 | state.push(')');
157 | }
158 | visit_funcSig(ast: HeronAstNode, state: CodeBuilder) {
159 | this.visitChildren(ast, state);
160 | state.pushLine('');
161 | }
162 | visit_genericConstraint(ast: HeronAstNode, state: CodeBuilder) {
163 | state.push(' : ');
164 | this.visitChildren(ast, state);
165 | }
166 | visit_genericParams(ast: HeronAstNode, state: CodeBuilder) {
167 | if (ast.children.length > 0) {
168 | // Put a space because in case of 'op<'
169 | state.push(' <');
170 | this.visitChildrenDelimited(ast, state, ', ');
171 | state.push('>');
172 | }
173 | this.visitChildren(ast, state);
174 | }
175 | visit_identifier(ast: HeronAstNode, state: CodeBuilder) {
176 | state.push(ast.allText);
177 | }
178 | visit_ifCond(ast: HeronAstNode, state: CodeBuilder) {
179 | state.push('if (');
180 | this.visitChildren(ast, state);
181 | state.push(')');
182 | }
183 | visit_intrinsicDef(ast: HeronAstNode, state: CodeBuilder) {
184 | state.push('intrinsic ')
185 | this.visitChildren(ast, state);
186 | state.pushLine(';');
187 | }
188 | visit_lambdaArg(ast: HeronAstNode, state: CodeBuilder) {
189 | this.visitNode(ast.children[0], state);
190 | if (ast.children.length > 1) {
191 | state.push(': ');
192 | this.visitNode(ast.children[1], state);
193 | }
194 | }
195 | visit_lambdaArgs(ast: HeronAstNode, state: CodeBuilder) {
196 | state.push('(');
197 | this.visitChildrenDelimited(ast, state, ', ');
198 | state.push(')');
199 | }
200 | visit_lambdaBody(ast: HeronAstNode, state: CodeBuilder) {
201 | state.push(' => ');
202 | this.visitChildren(ast, state);
203 | }
204 | visit_langDecl(ast: HeronAstNode, state: CodeBuilder) {
205 | state.push('language ');
206 | this.visitChildren(ast, state);
207 | state.pushLine(';');
208 | }
209 | visit_langVer(ast: HeronAstNode, state: CodeBuilder) {
210 | state.push(ast.allText);
211 | }
212 | visit_logicalAndOp(ast: HeronAstNode, state: CodeBuilder) {
213 | state.push(' ' + ast.allText + ' ');
214 | }
215 | visit_logicalOrOp(ast: HeronAstNode, state: CodeBuilder) {
216 | state.push(' ' + ast.allText + ' ');
217 | }
218 | visit_logicalXOrOp(ast: HeronAstNode, state: CodeBuilder) {
219 | state.push(' ' + ast.allText + ' ');
220 | }
221 | visit_loopCond(ast: HeronAstNode, state: CodeBuilder) {
222 | state.push('while (');
223 | this.visitChildren(ast, state);
224 | state.pushLine(')');
225 | }
226 | visit_moduleBody(ast: HeronAstNode, state: CodeBuilder) {
227 | state.pushLine('{');
228 | this.visitChildren(ast, state);
229 | state.pushLine('}');
230 | }
231 | visit_moduleName(ast: HeronAstNode, state: CodeBuilder) {
232 | state.push(ast.allText);
233 | }
234 | visit_multiplicativeOp(ast: HeronAstNode, state: CodeBuilder) {
235 | state.push(' ' + ast.allText + ' ');
236 | }
237 | visit_number(ast: HeronAstNode, state: CodeBuilder) {
238 | state.push(ast.allText);
239 | }
240 | visit_objectExpr(ast: HeronAstNode, state: CodeBuilder) {
241 | state.push('{ ')
242 | this.visitChildren(ast, state);
243 | state.push(' }');
244 | }
245 | visit_objectField(ast: HeronAstNode, state: CodeBuilder) {
246 | this.visitNode(ast.children[0], state);
247 | state.push(' = ');
248 | this.visitNode(ast.children[1], state);
249 | state.push('; ')
250 | }
251 | visit_opName(ast: HeronAstNode, state: CodeBuilder) {
252 | state.push(ast.allText);
253 | }
254 | visit_parenExpr(ast: HeronAstNode, state: CodeBuilder) {
255 | state.push('(');
256 | this.visitChildren(ast, state);
257 | state.push(')');
258 | }
259 | visit_postDecOp(ast: HeronAstNode, state: CodeBuilder) {
260 | this.visitChildren(ast, state);
261 | state.push('--')
262 | }
263 | visit_postIncOp(ast: HeronAstNode, state: CodeBuilder) {
264 | this.visitChildren(ast, state);
265 | state.push('++')
266 | }
267 | visit_prefixOp(ast: HeronAstNode, state: CodeBuilder) {
268 | state.push(ast.allText);
269 | }
270 | visit_rangeExpr(ast: HeronAstNode, state: CodeBuilder) {
271 | this.visitNode(ast.children[0], state);
272 | if (ast.children.length > 1) {
273 | state.push(' .. ');
274 | this.visitNode(ast.children[0], state);
275 | }
276 | }
277 | visit_recCompoundStatement(ast: HeronAstNode, state: CodeBuilder) {
278 | state.pushLine('{');
279 | this.visitChildren(ast, state);
280 | state.pushLine('}');
281 | }
282 | visit_relationalOp(ast: HeronAstNode, state: CodeBuilder) {
283 | state.push(' ' + ast.allText + ' ');
284 | }
285 | visit_returnStatement(ast: HeronAstNode, state: CodeBuilder) {
286 | state.push('return ');
287 | this.visitChildren(ast, state);
288 | state.pushLine(';');
289 | }
290 | visit_returnType(ast: HeronAstNode, state: CodeBuilder) {
291 | state.push(' : ');
292 | this.visitChildren(ast, state);
293 | }
294 | visit_singleQuotedStringContents(ast: HeronAstNode, state: CodeBuilder) {
295 | state.push("'" + ast.allText + "'");
296 | }
297 | visit_typeName(ast: HeronAstNode, state: CodeBuilder) {
298 | state.push(ast.allText);
299 | }
300 | visit_typeParamList(ast: HeronAstNode, state: CodeBuilder) {
301 | if (ast.children.length == 0) return;
302 | state.push('<');
303 | this.visitChildrenDelimited(ast, state, ', ');
304 | state.push('>');
305 | }
306 | visit_urn(ast: HeronAstNode, state: CodeBuilder) {
307 | this.visitChildrenDelimited(ast, state, ':');
308 | }
309 | visit_urnPart(ast: HeronAstNode, state: CodeBuilder) {
310 | state.push(ast.allText);
311 | }
312 | visit_varName(ast: HeronAstNode, state: CodeBuilder) {
313 | state.push(ast.allText);
314 | }
315 | visit_varDeclStatement(ast: HeronAstNode, state: CodeBuilder) {
316 | state.push('var ');
317 | this.visitChildren(ast, state);
318 | state.pushLine(';');
319 | }
320 | visit_varInitialization(ast: HeronAstNode, state: CodeBuilder) {
321 | state.push(' = ');
322 | this.visitChildren(ast, state);
323 | }
324 | visit_whileLoop(ast: HeronAstNode, state: CodeBuilder) {
325 | state.push('while (');
326 | this.visitNode(ast.children[0], state);
327 | state.pushLine(')')
328 | this.visitChildren(ast, state);
329 | this.visitNode(ast.children[1], state);
330 | }
331 | }
332 |
--------------------------------------------------------------------------------
/demo/demo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Heron Geometry Creation Demo
8 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
199 |
200 |
203 |
204 |
--------------------------------------------------------------------------------
/input/geometry-mesh.heron:
--------------------------------------------------------------------------------
1 | language heron:std:0.1;
2 |
3 | // The following pages are inspirations for some of this
4 | // https://prideout.net/blog/?p=44
5 | // https://github.com/mrdoob/three.js/blob/master/src/geometries/ParametricGeometry.js
6 | // https://github.com/mrdoob/three.js/blob/master/examples/js/ParametricGeometries.js
7 | // https://paulbourke.net/geometry/
8 |
9 | module heron:geometry.mesh:0.1
10 | {
11 | import heron:std.array:0.1;
12 | import heron:geometry.vector:0.1;
13 |
14 | // Alternative constructors
15 | function mesh(vertexBuffer)
16 | = mesh(vertexBuffer, vertexBuffer.indices);
17 |
18 | function mesh(vertexBuffer, indexBuffer)
19 | = mesh(vertexBuffer);
20 |
21 | function mesh(vertexBuffer, indexBuffer)
22 | = mesh(vertexBuffer, indexBuffer, origin.repeat(0));
23 |
24 | function mesh(vertexBuffer, indexBuffer, uvBuffer)
25 | = mesh(vertexBuffer, indexBuffer, uvBuffer, origin.repeat(0));
26 |
27 | function mesh(vertexBuffer, indexBuffer, uvBuffer, colorBuffer)
28 | = mesh(vertexBuffer, indexBuffer, uvBuffer, colorBuffer, origin.repeat(0))
29 | .computeVertexNormals();
30 |
31 | function setVertices(m, points)
32 | = mesh(points, m.indexBuffer, m.uvBuffer, m.colorBuffer, m.normalBuffer);
33 |
34 | function setVertexColors(m, colors)
35 | = mesh(m.vertexBuffer, m.indexBuffer, m.uvBuffer, colors, m.normalBuffer);
36 |
37 | function setVertexUVs(m, uvs)
38 | = mesh(m.vertexBuffer, m.indexBuffer, uvs, m.colorBuffer, m.normalBuffer);
39 |
40 | function setVertexNormals(m, normals)
41 | = mesh(m.vertexBuffer, m.indexBuffer, m.uvBuffer, m.colorBuffer, normals);
42 |
43 | // Platonic solids
44 | var tetrahedron
45 | = mesh(
46 | [
47 | 1, 1, 1,
48 | -1, -1, 1,
49 | -1, 1, -1,
50 | 1, -1, -1
51 | ].toVectors,
52 | [
53 | 2, 1, 0,
54 | 0, 3, 2,
55 | 1, 3, 0,
56 | 2, 3, 1
57 | ]);
58 |
59 | var cube
60 | = mesh(
61 | [
62 | // front
63 | -1.0, -1.0, 1.0,
64 | 1.0, -1.0, 1.0,
65 | 1.0, 1.0, 1.0,
66 | -1.0, 1.0, 1.0,
67 | // back
68 | -1.0, -1.0, -1.0,
69 | 1.0, -1.0, -1.0,
70 | 1.0, 1.0, -1.0,
71 | -1.0, 1.0, -1.0
72 | ].toVectors,
73 | [
74 | // front
75 | 0, 1, 2, 2, 3, 0,
76 | // right
77 | 1, 5, 6, 6, 2, 1,
78 | // back
79 | 7, 6, 5, 5, 4, 7,
80 | // left
81 | 4, 0, 3, 3, 7, 4,
82 | // bottom
83 | 4, 5, 1, 1, 0, 4,
84 | // top
85 | 3, 2, 6, 6, 7, 3
86 | ]);
87 |
88 | // https://github.com/mrdoob/three.js/blob/master/src/geometries/OctahedronGeometry.js
89 | var octahedron
90 | = mesh(
91 | [
92 | 1, 0, 0, -1, 0, 0, 0, 1, 0,
93 | 0, -1, 0, 0, 0, 1, 0, 0, -1
94 | ].toVectors,
95 | [
96 | 0, 2, 4, 0, 4, 3, 0, 3, 5,
97 | 0, 5, 2, 1, 2, 5, 1, 5, 3,
98 | 1, 3, 4, 1, 4, 2
99 | ]);
100 |
101 | // https://github.com/mrdoob/three.js/blob/master/src/geometries/DodecahedronGeometry.js
102 | var dodecahedron =
103 | var t = (1 + sqrt(5)) / 2 in
104 | var r = 1 / t in
105 | mesh([
106 | -1, -1, -1, -1, -1, 1,
107 | -1, 1, -1, -1, 1, 1,
108 | 1, -1, -1, 1, -1, 1,
109 | 1, 1, -1, 1, 1, 1,
110 |
111 | // (0, +/-1/theta, +/-theta)
112 | 0, -r, -t, 0, -r, t,
113 | 0, r, -t, 0, r, t,
114 |
115 | // (+/-1/theta, +/-theta, 0)
116 | -r, -t, 0, -r, t, 0,
117 | r, -t, 0, r, t, 0,
118 |
119 | // (+/-theta, 0, +/-1/theta)
120 | -t, 0, -r, t, 0, -r,
121 | -t, 0, r, t, 0, r
122 | ].toVectors,
123 | [
124 | 3, 11, 7, 3, 7, 15, 3, 15, 13,
125 | 7, 19, 17, 7, 17, 6, 7, 6, 15,
126 | 17, 4, 8, 17, 8, 10, 17, 10, 6,
127 | 8, 0, 16, 8, 16, 2, 8, 2, 10,
128 | 0, 12, 1, 0, 1, 18, 0, 18, 16,
129 | 6, 10, 2, 6, 2, 13, 6, 13, 15,
130 | 2, 16, 18, 2, 18, 3, 2, 3, 13,
131 | 18, 1, 9, 18, 9, 11, 18, 11, 3,
132 | 4, 14, 12, 4, 12, 0, 4, 0, 8,
133 | 11, 9, 5, 11, 5, 19, 11, 19, 7,
134 | 19, 5, 14, 19, 14, 4, 19, 4, 17,
135 | 1, 12, 14, 1, 14, 5, 1, 5, 9
136 | ]);
137 |
138 |
139 | // https://github.com/mrdoob/three.js/blob/master/src/geometries/IcosahedronGeometry.js
140 | var icosahedron
141 | = var t = (1 + sqrt(5)) / 2 in
142 | mesh([
143 | -1, t, 0, 1, t, 0, -1, -t, 0, 1, -t, 0,
144 | 0, -1, t, 0, 1, t, 0, -1, -t, 0, 1, -t,
145 | t, 0, -1, t, 0, 1, -t, 0, -1, -t, 0, 1
146 | ].toVectors,
147 | [
148 | 0, 11, 5, 0, 5, 1, 0, 1, 7, 0, 7, 10, 0, 10, 11,
149 | 1, 5, 9, 5, 11, 4, 11, 10, 2, 10, 7, 6, 7, 1, 8,
150 | 3, 9, 4, 3, 4, 2, 3, 2, 6, 3, 6, 8, 3, 8, 9,
151 | 4, 9, 5, 2, 4, 11, 6, 2, 10, 8, 6, 7, 9, 8, 1
152 | ]);
153 |
154 | // Parametric geometry creation
155 |
156 | // Given a number of points (vectors) arranged as a quad strip of "rows"
157 | // will return a set of indices representing the triangulated indices.
158 | function quadStripToMeshIndices(vertices, rows, connectRows, connectCols) {
159 | var cols = vertices.count / rows;
160 | var nr = connectRows ? rows : rows-1;
161 | var nc = connectCols ? cols : cols-1;
162 | var indices = [].mutable;
163 | for (var row in 0..nr) {
164 | for (var col in 0..nc) {
165 | // Rows increase from bottom to top
166 | // Columns increase from left to right
167 | // row r + 1 = ... d c ...
168 | // row r = ... a b ...
169 | var a = col + row * cols;
170 | var b = (col+1) % (cols) + row * cols;
171 | var c = (col+1) % (cols) + (row+1) % (rows) * cols;
172 | var d = col + (row+1) % (rows) * cols;
173 | indices = indices.pushMany([a, b, d]);
174 | indices = indices.pushMany([b, c, d]);
175 | }
176 | }
177 | return indices.immutable;
178 | }
179 |
180 | // Converts from UV coordinates to a float3
181 | function vector(uv: Float2)
182 | = float3(-uv.x.cos * uv.y.sin, uv.x.cos, uv.x.sin * uv.y.sin);
183 |
184 | // Works for Float, Float2, Float3, and Float4
185 | function rescale(v, from, length)
186 | = from + (v * length);
187 |
188 | // Given a function that converts UV coordinates to vectors, creates an
189 | // array of points (uCount x vCount) starting at uStart,vStart (0,0) going to
190 | // uLength, vLength (1,1)
191 | function meshFromUV(f, uCount, vCount, uStart, vStart, uLength, vLength, uJoin, vJoin) {
192 | var uMax = uJoin ? uCount.float : (uCount - 1).float;
193 | var vMax = vJoin ? vCount.float : (vCount - 1).float;
194 | var uvs = cartesianProduct(0..uCount, 0..vCount, (u, v)
195 | => vector(u / uMax * uLength + uStart, v / vMax * vLength + vStart, 0));
196 | var points = uvs.map(uvw => f(uvw.x, uvw.y));
197 | var normals = uvs.map(uvw => normalFromUV(uvw.x, uvw.y, f));
198 | var indices = quadStripToMeshIndices(points, vCount, uJoin, vJoin);
199 | return mesh(points, indices, uvs, origin.repeat(0), normals);
200 | }
201 |
202 | function meshFromUV(f, segments)
203 | = meshFromUV(f, segments, true);
204 |
205 | function meshFromUV(f, segments, join)
206 | = meshFromUV(f, segments, segments, 0.0, 0.0, 1.0, 1.0, join, join);
207 |
208 | // Given UV coordinates on the surface of a sphere u=[0,1), v=[0,1) computes the 3D location.
209 | function spherePoint(u, v)
210 | = vector(-cos(u*2.0*pi) * sin(v*2.0*pi), cos(v*2.0*pi), sin(u*2.0*pi) * sin(v*2.0*pi));
211 |
212 | function sphere(segments)
213 | = meshFromUV(spherePoint, segments);
214 |
215 | function sphere()
216 | = sphere(32);
217 |
218 | // Given UV coordinates on the surface of a cylinder u=[0,1), v=[0,1) computes the 3D location.
219 | function cylinderPoint(u, v)
220 | = vector(sin(u*2.0*pi), v * 2, cos(u*2.0*pi));
221 |
222 | function cylinder(segments)
223 | = meshFromUV(cylinderPoint, segments);
224 |
225 | function cylinder()
226 | = cylinder(16);
227 |
228 | function torus(r1, r2, segments)
229 | = meshFromUV((u, v) => torusPoint(u, v, r1, r2), segments);
230 |
231 | // Given UV coordinates u=[0,1), v=[0,1), and a major radius (donut)
232 | // and a minor radius (tube) computes the 3D location of the torus
233 | function torusPoint(u, v, r1, r2)
234 | = vector(
235 | (r1 + r2 * cos(v*2.0*pi)) * cos(u*2.0*pi),
236 | (r1 + r2 * cos(v*2.0*pi)) * sin(u*2.0*pi),
237 | r2 * sin(v*2.0*pi));
238 |
239 | function tangentFromUV(u, v, f, p: Float3, eps: Float)
240 | = u - eps >= 0
241 | ? p - f(u - eps, v)
242 | : f(u + eps, v) - p;
243 |
244 | function cotangentFromUV(u, v, f, p: Float3, eps: Float)
245 | = v - eps >= 0
246 | ? p - f(u, v - eps)
247 | : f(u, v + eps) - p;
248 |
249 | // Given a function that generates coordinates from UV space will compute a local normal.
250 | function normalFromUV(u, v, f)
251 | = var eps = 0.0001 in
252 | var p = f(u, v) in
253 | cross(tangentFromUV(u, v, f, p, eps), cotangentFromUV(u, v, f, p, eps)).normal;
254 |
255 | function torus()
256 | = torus(2, 0.5, 32);
257 |
258 | function vertexCount(mesh)
259 | = mesh.vertexBuffer.count;
260 |
261 | function faceCount(mesh)
262 | = mesh.indexBuffer.count / 3;
263 |
264 | function toVectors(xs)
265 | = (0 .. xs.count/3).map(i => vector(xs[i*3], xs[i*3+1], xs[i*3+2]));
266 |
267 | function transform(m, f)
268 | = m.setVertices(m.vertexBuffer.map(f));
269 |
270 | function translate(m, amount)
271 | = m.transform(v => v + amount);
272 |
273 | function translate(m, vectors)
274 | = m.setVertices(m.vertexBuffer.zip(vectors, (p, v) => p + v));
275 |
276 | function scale(m, amount)
277 | = m.transform(v => v * amount);
278 |
279 | function kleinPoint(a, b) {
280 | var u = a * pi * 2.0;
281 | var v = b * pi * 2.0;
282 | var x = 0.0, y = 0.0, z = 0.0;
283 | if (u < pi) {
284 | x = 3.0 * u.cos * (1.0 + u.sin) + (2.0 * (1.0 - u.cos / 2.0)) * u.cos * v.cos;
285 | z = -8.0 * u.sin - 2.0 * (1.0 - u.cos / 2.0) * u.sin * v.cos;
286 | } else {
287 | x = 3.0 * u.cos * (1.0 + u.sin) + (2.0 * (1.0 - u.cos / 2.0)) * cos(v + pi);
288 | z = -8.0 * u.sin;
289 | }
290 | y = -2.0 * (1.0 - u.cos / 2.0) * v.sin;
291 | // Scaled down, because it is too big compared to other primitives
292 | // TODO: find a more well thoughts out scale factor (is it pi?)
293 | return vector(x / 4.0, y / 4.0, z / 4.0);
294 | }
295 |
296 | function klein()
297 | = meshFromUV(kleinPoint, 32, false);
298 |
299 | function planeXYPoint(u, v) = vector(u, v, 0);
300 | function planeXZPoint(u, v) = vector(u, 0, v);
301 | function planeYZPoint(u, v) = vector(0, u, v);
302 | function planeYXPoint(u, v) = vector(v, u, 0);
303 | function planeZXPoint(u, v) = vector(v, 0, u);
304 | function planeZYPoint(u, v) = vector(0, v, u);
305 |
306 | function plane()
307 | = meshFromUV(planeXYPoint, 16, false);
308 |
309 | function mobiusPoint(a, b) {
310 | var u = a - 0.5;
311 | var v = b * 2.0 * pi;
312 | return vector(
313 | v.cos * (2 + u * cos( v / 2 )),
314 | v.sin * (2 + u * cos( v / 2 )),
315 | u * sin( v / 2 ));
316 | }
317 |
318 | function mobius()
319 | = meshFromUV(mobiusPoint, 20, false);
320 |
321 | function facePoint(mesh, f, i)
322 | = mesh.vertexBuffer[mesh.indexBuffer[f * 3 + i]];
323 |
324 | function faceNormal(mesh, f)
325 | = cross(mesh.faceSide1(f), mesh.faceSide2(f)).normal;
326 |
327 | function faceSide1(mesh, f)
328 | = mesh.facePoint(f, 1) - mesh.facePoint(f, 0);
329 |
330 | function faceSide2(mesh, f)
331 | = mesh.facePoint(f, 2) - mesh.facePoint(f, 0);
332 |
333 | function faceNormals(mesh)
334 | = (0..mesh.faceCount).map(i => mesh.faceNormal(i));
335 |
336 | function computeVertexNormals(mesh) {
337 | var sums = origin.repeat(mesh.vertexCount).mutable;
338 | var counts = repeat(0, mesh.vertexCount).mutable;
339 | for (var f in 0..mesh.faceCount) {
340 | var normal = mesh.faceNormal(f);
341 | for (var i in 0..3) {
342 | var index = mesh.indexBuffer[f*3 + i];
343 | sums[index] += normal;
344 | counts[index] += 1;
345 | }
346 | }
347 | return mesh.setVertexNormals(zip(sums, counts, (a, b) => (a / b.float.vector)));
348 | }
349 |
350 | // Rotation is represented as a quaternion
351 | function applyRotation(pos, rotXYZ, rotW)
352 | = pos + 2.0.vector * cross( rotXYZ, cross( rotXYZ, pos ) + rotW * pos );
353 |
354 | // Rotation is represented as a quaternion
355 | function applyTRS(pos, trans, rotXYZ, rotW, scale)
356 | = applyRotation(pos * scale, rotXYZ, rotW) + trans;
357 | }
--------------------------------------------------------------------------------