openalgo-script 0.1.0-alpha.0 → 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/core/index.ts CHANGED
@@ -144,3 +144,4 @@ export {
144
144
  export { lex } from './lex/index.js';
145
145
 
146
146
  export { parse, parseTokens } from './parse/index.js';
147
+ export { COMPILED_FORMAT_VERSION, VERSION } from './version/index.js';
@@ -1,109 +1,109 @@
1
- import type { Block, Expression, FunctionDeclaration, Name, Parameter } from '../ast/index.js';
2
- import { makeNode } from '../ast/index.js';
3
- import { spanning } from '../span/index.js';
4
- import type { Token } from '../tokens/index.js';
5
- import { parseTypeAnnotation } from './annotations.js';
6
- import { parseBlock } from './blocks.js';
7
- import type { Cursor } from './cursor.js';
8
- import { parseBracketedList, parseExpression } from './expressions.js';
9
- import { takeName } from './names.js';
10
- import { finishStatement } from './statements.js';
11
-
12
- /**
13
- * `fn name(parameters) => body`, language.md 11.1.
14
- *
15
- * The body is an expression when it is written after the `=>` on one line, and
16
- * a block when the `=>` ends the line. Both forms exist because the one line
17
- * form needs the implicit return and an early exit needs the explicit one.
18
- *
19
- * The `=>` is two tokens. Section 3.12 does not list it among the punctuation,
20
- * so it arrives as an `=` and a `>` that touch, and touching is what tells it
21
- * from a comparison: the lexer suppresses its continuation rule for that exact
22
- * pair, and the same adjacency test is what matches it here.
23
- */
24
- export function parseFunctionDeclaration(cursor: Cursor): FunctionDeclaration {
25
- const keyword = cursor.advance();
26
- const name = takeName(cursor);
27
-
28
- const open = cursor.take('(');
29
- if (open === undefined) cursor.unexpected(cursor.token);
30
- const parameters =
31
- open === undefined ? [] : parseBracketedList(cursor, open, parseParameter).items;
32
-
33
- const arrow = takeArrow(cursor);
34
- if (arrow === undefined) {
35
- return declaration(keyword, name, parameters, headerWithNoArrow(cursor, keyword));
36
- }
37
-
38
- if (!cursor.atStatementEnd()) {
39
- const body = parseExpression(cursor);
40
- finishStatement(cursor);
41
- return declaration(keyword, name, parameters, body);
42
- }
43
-
44
- finishStatement(cursor);
45
- return declaration(keyword, name, parameters, parseBlock(cursor, 'fn', keyword.span));
46
- }
47
-
48
- /**
49
- * A parameter, which is an ordinary identifier and not a label.
50
- *
51
- * `fn f(color = red)` is OS1019, and `plot(v, "V", color = aqua)` is not: the
52
- * body of a function refers to its parameters, and nothing ever refers to a
53
- * label (language.md 3.4). `takeName` is the whole of that difference.
54
- */
55
- function parseParameter(cursor: Cursor): Parameter {
56
- const name = takeName(cursor);
57
-
58
- const annotation = cursor.take(':') !== undefined ? parseTypeAnnotation(cursor) : undefined;
59
- const defaultValue = cursor.take('=') !== undefined ? parseExpression(cursor) : undefined;
60
-
61
- const end = defaultValue?.span ?? annotation?.span ?? name.span;
62
- return makeNode('parameter', spanning(name.span, end), { name, annotation, defaultValue });
63
- }
64
-
65
- /** The `=>` of 11.1: an `=` and a `>` written against each other. */
66
- function takeArrow(cursor: Cursor): Token | undefined {
67
- const equals = cursor.token;
68
- const greater = cursor.peek();
69
- if (equals.kind !== '=' || greater.kind !== '>') return undefined;
70
- if (equals.span.offset + equals.span.length !== greater.span.offset) return undefined;
71
-
72
- cursor.advance();
73
- return cursor.advance();
74
- }
75
-
76
- /**
77
- * A header with no `=>`, which the catalogue has no code of its own for.
78
- *
79
- * What is reported is what is true. A token sitting where the arrow belongs is
80
- * OS1018, whose fix names the operator that was meant to join the two halves. A
81
- * header that simply ends is OS1010, because that is what it now is: a header
82
- * with no body, since without the arrow the lexer opened no block under it.
83
- */
84
- function headerWithNoArrow(cursor: Cursor, keyword: Token): Block | Expression {
85
- if (!cursor.atStatementEnd()) {
86
- cursor.unexpected(cursor.token);
87
- const body = parseExpression(cursor);
88
- finishStatement(cursor);
89
- return body;
90
- }
91
-
92
- cursor.report('OS1010', keyword.span, { header: 'fn' });
93
- finishStatement(cursor);
94
- if (cursor.at('indent')) return parseBlock(cursor, 'fn', keyword.span);
95
- return makeNode('block', cursor.holeSpan(), { statements: [] });
96
- }
97
-
98
- function declaration(
99
- keyword: Token,
100
- name: Name,
101
- parameters: readonly Parameter[],
102
- body: Block | Expression,
103
- ): FunctionDeclaration {
104
- return makeNode('functionDeclaration', spanning(keyword.span, body.span), {
105
- name,
106
- parameters,
107
- body,
108
- });
109
- }
1
+ import type { Block, Expression, FunctionDeclaration, Name, Parameter } from '../ast/index.js';
2
+ import { makeNode } from '../ast/index.js';
3
+ import { spanning } from '../span/index.js';
4
+ import type { Token } from '../tokens/index.js';
5
+ import { parseTypeAnnotation } from './annotations.js';
6
+ import { parseBlock } from './blocks.js';
7
+ import type { Cursor } from './cursor.js';
8
+ import { parseBracketedList, parseExpression } from './expressions.js';
9
+ import { takeName } from './names.js';
10
+ import { finishStatement } from './statements.js';
11
+
12
+ /**
13
+ * `fn name(parameters) => body`, language.md 11.1.
14
+ *
15
+ * The body is an expression when it is written after the `=>` on one line, and
16
+ * a block when the `=>` ends the line. Both forms exist because the one line
17
+ * form needs the implicit return and an early exit needs the explicit one.
18
+ *
19
+ * The `=>` is two tokens. Section 3.12 does not list it among the punctuation,
20
+ * so it arrives as an `=` and a `>` that touch, and touching is what tells it
21
+ * from a comparison: the lexer suppresses its continuation rule for that exact
22
+ * pair, and the same adjacency test is what matches it here.
23
+ */
24
+ export function parseFunctionDeclaration(cursor: Cursor): FunctionDeclaration {
25
+ const keyword = cursor.advance();
26
+ const name = takeName(cursor);
27
+
28
+ const open = cursor.take('(');
29
+ if (open === undefined) cursor.unexpected(cursor.token);
30
+ const parameters =
31
+ open === undefined ? [] : parseBracketedList(cursor, open, parseParameter).items;
32
+
33
+ const arrow = takeArrow(cursor);
34
+ if (arrow === undefined) {
35
+ return declaration(keyword, name, parameters, headerWithNoArrow(cursor, keyword));
36
+ }
37
+
38
+ if (!cursor.atStatementEnd()) {
39
+ const body = parseExpression(cursor);
40
+ finishStatement(cursor);
41
+ return declaration(keyword, name, parameters, body);
42
+ }
43
+
44
+ finishStatement(cursor);
45
+ return declaration(keyword, name, parameters, parseBlock(cursor, 'fn', keyword.span));
46
+ }
47
+
48
+ /**
49
+ * A parameter, which is an ordinary identifier and not a label.
50
+ *
51
+ * `fn f(color = red)` is OS1019, and `plot(v, "V", color = aqua)` is not: the
52
+ * body of a function refers to its parameters, and nothing ever refers to a
53
+ * label (language.md 3.4). `takeName` is the whole of that difference.
54
+ */
55
+ function parseParameter(cursor: Cursor): Parameter {
56
+ const name = takeName(cursor);
57
+
58
+ const annotation = cursor.take(':') !== undefined ? parseTypeAnnotation(cursor) : undefined;
59
+ const defaultValue = cursor.take('=') !== undefined ? parseExpression(cursor) : undefined;
60
+
61
+ const end = defaultValue?.span ?? annotation?.span ?? name.span;
62
+ return makeNode('parameter', spanning(name.span, end), { name, annotation, defaultValue });
63
+ }
64
+
65
+ /** The `=>` of 11.1: an `=` and a `>` written against each other. */
66
+ function takeArrow(cursor: Cursor): Token | undefined {
67
+ const equals = cursor.token;
68
+ const greater = cursor.peek();
69
+ if (equals.kind !== '=' || greater.kind !== '>') return undefined;
70
+ if (equals.span.offset + equals.span.length !== greater.span.offset) return undefined;
71
+
72
+ cursor.advance();
73
+ return cursor.advance();
74
+ }
75
+
76
+ /**
77
+ * A header with no `=>`, which the catalogue has no code of its own for.
78
+ *
79
+ * What is reported is what is true. A token sitting where the arrow belongs is
80
+ * OS1018, whose fix names the operator that was meant to join the two halves. A
81
+ * header that simply ends is OS1010, because that is what it now is: a header
82
+ * with no body, since without the arrow the lexer opened no block under it.
83
+ */
84
+ function headerWithNoArrow(cursor: Cursor, keyword: Token): Block | Expression {
85
+ if (!cursor.atStatementEnd()) {
86
+ cursor.unexpected(cursor.token);
87
+ const body = parseExpression(cursor);
88
+ finishStatement(cursor);
89
+ return body;
90
+ }
91
+
92
+ cursor.report('OS1010', keyword.span, { header: 'fn' });
93
+ finishStatement(cursor);
94
+ if (cursor.at('indent')) return parseBlock(cursor, 'fn', keyword.span);
95
+ return makeNode('block', cursor.holeSpan(), { statements: [] });
96
+ }
97
+
98
+ function declaration(
99
+ keyword: Token,
100
+ name: Name,
101
+ parameters: readonly Parameter[],
102
+ body: Block | Expression,
103
+ ): FunctionDeclaration {
104
+ return makeNode('functionDeclaration', spanning(keyword.span, body.span), {
105
+ name,
106
+ parameters,
107
+ body,
108
+ });
109
+ }
@@ -1,105 +1,105 @@
1
- import type { Expression, SwitchCase, SwitchDefault, SwitchStatement } from '../ast/index.js';
2
- import { makeNode } from '../ast/index.js';
3
- import type { Span } from '../span/index.js';
4
- import { spanning } from '../span/index.js';
5
- import { parseBlock } from './blocks.js';
6
- import type { Cursor } from './cursor.js';
7
- import { parseCondition } from './expressions.js';
8
- import { finishStatement } from './statements.js';
9
-
10
- /**
11
- * `switch`, in both forms of language.md 10.6.
12
- *
13
- * With a subject each case value is compared against it, and without one each
14
- * case value is a condition. The difference is the absent subject and needs no
15
- * flag beside it.
16
- *
17
- * The arms are the whole of a switch body. A `default` that is not last would
18
- * leave the arms after it unreachable, so the arm that follows one is OS1017,
19
- * and the arm is kept in the tree all the same: a reader who moves the default
20
- * should not then be told about the arms one at a time.
21
- */
22
- export function parseSwitchStatement(cursor: Cursor): SwitchStatement {
23
- const keyword = cursor.advance();
24
- const subject = cursor.atStatementEnd() ? undefined : parseCondition(cursor);
25
- finishStatement(cursor);
26
-
27
- const cases: SwitchCase[] = [];
28
- let defaultCase: SwitchDefault | undefined;
29
- let end: Span = keyword.span;
30
-
31
- if (!cursor.at('indent')) {
32
- if (!cursor.reportedHere) cursor.report('OS1010', keyword.span, { header: 'switch' });
33
- return makeNode('switchStatement', spanning(keyword.span, end), {
34
- subject,
35
- cases,
36
- defaultCase,
37
- });
38
- }
39
-
40
- if (!cursor.openBlock(cursor.token.span)) {
41
- cursor.skipBlockBody();
42
- return makeNode('switchStatement', spanning(keyword.span, end), {
43
- subject,
44
- cases,
45
- defaultCase,
46
- });
47
- }
48
- cursor.advance();
49
-
50
- while (!cursor.at('dedent') && !cursor.at('endOfFile')) {
51
- const before = cursor.position;
52
- const word = cursor.token;
53
-
54
- if (cursor.at('case')) {
55
- if (defaultCase !== undefined) cursor.report('OS1017', word.span, { word: word.text });
56
- const arm = parseCase(cursor);
57
- cases.push(arm);
58
- end = arm.span;
59
- } else if (cursor.at('default')) {
60
- const arm = parseDefault(cursor);
61
- if (defaultCase === undefined) defaultCase = arm;
62
- else cursor.report('OS1017', word.span, { word: word.text });
63
- end = arm.span;
64
- } else {
65
- // A line in a switch body that is not an arm. The catalogue has no code
66
- // for it, and OS1017 carries the rule it breaks: the arms are the whole
67
- // of the body, so this line belongs inside one of them or above the
68
- // switch.
69
- cursor.report('OS1017', word.span, { word: 'case' });
70
- cursor.skipStatement();
71
- }
72
-
73
- if (cursor.position === before) cursor.skipStatement();
74
- }
75
-
76
- cursor.closeBlock();
77
- cursor.take('dedent');
78
-
79
- return makeNode('switchStatement', spanning(keyword.span, end), { subject, cases, defaultCase });
80
- }
81
-
82
- /** One arm. Several values separated by commas are one arm, not several. */
83
- function parseCase(cursor: Cursor): SwitchCase {
84
- cursor.beginStatement();
85
- const keyword = cursor.advance();
86
-
87
- const values: Expression[] = [parseCondition(cursor)];
88
- while (cursor.take(',') !== undefined) {
89
- const before = cursor.position;
90
- values.push(parseCondition(cursor));
91
- if (cursor.position === before) break;
92
- }
93
-
94
- finishStatement(cursor);
95
- const body = parseBlock(cursor, 'case', keyword.span);
96
- return makeNode('switchCase', spanning(keyword.span, body.span), { values, body });
97
- }
98
-
99
- function parseDefault(cursor: Cursor): SwitchDefault {
100
- cursor.beginStatement();
101
- const keyword = cursor.advance();
102
- finishStatement(cursor);
103
- const body = parseBlock(cursor, 'default', keyword.span);
104
- return makeNode('switchDefault', spanning(keyword.span, body.span), { body });
105
- }
1
+ import type { Expression, SwitchCase, SwitchDefault, SwitchStatement } from '../ast/index.js';
2
+ import { makeNode } from '../ast/index.js';
3
+ import type { Span } from '../span/index.js';
4
+ import { spanning } from '../span/index.js';
5
+ import { parseBlock } from './blocks.js';
6
+ import type { Cursor } from './cursor.js';
7
+ import { parseCondition } from './expressions.js';
8
+ import { finishStatement } from './statements.js';
9
+
10
+ /**
11
+ * `switch`, in both forms of language.md 10.6.
12
+ *
13
+ * With a subject each case value is compared against it, and without one each
14
+ * case value is a condition. The difference is the absent subject and needs no
15
+ * flag beside it.
16
+ *
17
+ * The arms are the whole of a switch body. A `default` that is not last would
18
+ * leave the arms after it unreachable, so the arm that follows one is OS1017,
19
+ * and the arm is kept in the tree all the same: a reader who moves the default
20
+ * should not then be told about the arms one at a time.
21
+ */
22
+ export function parseSwitchStatement(cursor: Cursor): SwitchStatement {
23
+ const keyword = cursor.advance();
24
+ const subject = cursor.atStatementEnd() ? undefined : parseCondition(cursor);
25
+ finishStatement(cursor);
26
+
27
+ const cases: SwitchCase[] = [];
28
+ let defaultCase: SwitchDefault | undefined;
29
+ let end: Span = keyword.span;
30
+
31
+ if (!cursor.at('indent')) {
32
+ if (!cursor.reportedHere) cursor.report('OS1010', keyword.span, { header: 'switch' });
33
+ return makeNode('switchStatement', spanning(keyword.span, end), {
34
+ subject,
35
+ cases,
36
+ defaultCase,
37
+ });
38
+ }
39
+
40
+ if (!cursor.openBlock(cursor.token.span)) {
41
+ cursor.skipBlockBody();
42
+ return makeNode('switchStatement', spanning(keyword.span, end), {
43
+ subject,
44
+ cases,
45
+ defaultCase,
46
+ });
47
+ }
48
+ cursor.advance();
49
+
50
+ while (!cursor.at('dedent') && !cursor.at('endOfFile')) {
51
+ const before = cursor.position;
52
+ const word = cursor.token;
53
+
54
+ if (cursor.at('case')) {
55
+ if (defaultCase !== undefined) cursor.report('OS1017', word.span, { word: word.text });
56
+ const arm = parseCase(cursor);
57
+ cases.push(arm);
58
+ end = arm.span;
59
+ } else if (cursor.at('default')) {
60
+ const arm = parseDefault(cursor);
61
+ if (defaultCase === undefined) defaultCase = arm;
62
+ else cursor.report('OS1017', word.span, { word: word.text });
63
+ end = arm.span;
64
+ } else {
65
+ // A line in a switch body that is not an arm. The catalogue has no code
66
+ // for it, and OS1017 carries the rule it breaks: the arms are the whole
67
+ // of the body, so this line belongs inside one of them or above the
68
+ // switch.
69
+ cursor.report('OS1017', word.span, { word: 'case' });
70
+ cursor.skipStatement();
71
+ }
72
+
73
+ if (cursor.position === before) cursor.skipStatement();
74
+ }
75
+
76
+ cursor.closeBlock();
77
+ cursor.take('dedent');
78
+
79
+ return makeNode('switchStatement', spanning(keyword.span, end), { subject, cases, defaultCase });
80
+ }
81
+
82
+ /** One arm. Several values separated by commas are one arm, not several. */
83
+ function parseCase(cursor: Cursor): SwitchCase {
84
+ cursor.beginStatement();
85
+ const keyword = cursor.advance();
86
+
87
+ const values: Expression[] = [parseCondition(cursor)];
88
+ while (cursor.take(',') !== undefined) {
89
+ const before = cursor.position;
90
+ values.push(parseCondition(cursor));
91
+ if (cursor.position === before) break;
92
+ }
93
+
94
+ finishStatement(cursor);
95
+ const body = parseBlock(cursor, 'case', keyword.span);
96
+ return makeNode('switchCase', spanning(keyword.span, body.span), { values, body });
97
+ }
98
+
99
+ function parseDefault(cursor: Cursor): SwitchDefault {
100
+ cursor.beginStatement();
101
+ const keyword = cursor.advance();
102
+ finishStatement(cursor);
103
+ const body = parseBlock(cursor, 'default', keyword.span);
104
+ return makeNode('switchDefault', spanning(keyword.span, body.span), { body });
105
+ }
@@ -0,0 +1 @@
1
+ export { COMPILED_FORMAT_VERSION, VERSION } from './version.generated.js';
@@ -0,0 +1,15 @@
1
+ // Generated by scripts/generate-version.mjs. Do not edit.
2
+ //
3
+ // VERSION comes from package.json and COMPILED_FORMAT_VERSION from
4
+ // spec/compiled-program.md, so neither can drift from the thing it names.
5
+
6
+ /** This build's package version. The release refuses to publish unless the tag, the manifest and this agree. */
7
+ export const VERSION = "0.1.0-alpha.1";
8
+
9
+ /**
10
+ * The compiled program format this compiler emits.
11
+ *
12
+ * Deliberately not the package version. An engine written by somebody else
13
+ * targets this number, and it moves only when the format does.
14
+ */
15
+ export const COMPILED_FORMAT_VERSION = "1.0";