diff --git a/.github/workflows/npm-publish.yaml b/.github/workflows/npm-publish.yaml index 7af6978..080009d 100644 --- a/.github/workflows/npm-publish.yaml +++ b/.github/workflows/npm-publish.yaml @@ -7,15 +7,17 @@ on: jobs: publish-npm: runs-on: ubuntu-latest + permissions: + contents: read + id-token: write steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v6 with: - node-version: 14 + node-version: 24 registry-url: https://registry.npmjs.org/ - run: yarn && yarn compile - - run: npm publish - env: - NODE_AUTH_TOKEN: ${{secrets.npm_token}} + - run: npm publish --provenance --access public diff --git a/.github/workflows/upload.yaml b/.github/workflows/upload.yaml index 7d26502..b722564 100644 --- a/.github/workflows/upload.yaml +++ b/.github/workflows/upload.yaml @@ -10,16 +10,16 @@ jobs: upload-assets: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Use Node.js - uses: actions/setup-node@v2 + uses: actions/setup-node@v6 - name: Get yarn cache id: yarn-cache - run: echo "::set-output name=dir::$(yarn cache dir)" + run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - - uses: actions/cache@v2 + - uses: actions/cache@v4 with: path: ${{ steps.yarn-cache.outputs.dir }} key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} diff --git a/package.json b/package.json index 519789f..681b005 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cirru/parser.ts", - "version": "0.0.6", + "version": "0.0.7", "description": "Cirru Parser in TypeScript", "main": "lib/index.js", "scripts": { @@ -16,6 +16,14 @@ "keywords": [], "author": "", "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/Cirru/parser.ts.git" + }, + "publishConfig": { + "access": "public", + "provenance": true + }, "devDependencies": { "@emotion/css": "^11.11.2", "@types/jest": "^29.5.3", diff --git a/src/index.ts b/src/index.ts index e8c166e..4ee4e1d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -288,6 +288,18 @@ let resolveIndentations = (initialTokens: LexList) => { } }; +/** + * Parse Cirru code into an abstract syntax tree (AST). + * Handles indentation-based syntax and returns an array of expressions. + * + * @param code - Cirru source code string to parse + * @returns Array of parsed expressions (ICirruNode[]) + * @example + * ```typescript + * parse("defn square (x)\n * x x") + * // Returns: [["defn", "square", ["x"], ["*", "x", "x"]]] + * ``` + */ export let parse = (code: string) => { let tokens = resolveIndentations(lex(code)); let pointer = 0; @@ -298,3 +310,27 @@ export let parse = (code: string) => { }; return resolveComma(resolveDollar(buildExprs(pullToken))); }; + +/** + * Parse a single Cirru expression from a one-liner code string. + * Throws an error if the code contains zero or multiple expressions. + * + * @param code - Cirru source code string containing exactly one expression + * @returns Single parsed expression (ICirruNode) + * @throws {Error} If the code doesn't contain exactly one expression + * @example + * ```typescript + * parseOneLiner("add 1 2") + * // Returns: ["add", "1", "2"] + * + * parseOneLiner("(add 1 2)") + * // Returns: ["add", "1", "2"] + * ``` + */ +export let parseOneLiner = (code: string) => { + let result = parse(code); + if (result.length !== 1) { + throw new Error(`Expected single expression, got ${result.length} expressions`); + } + return result[0]; +}; diff --git a/src/parser.test.ts b/src/parser.test.ts index e340987..676ef08 100644 --- a/src/parser.test.ts +++ b/src/parser.test.ts @@ -1,6 +1,6 @@ let fs = require("fs"); let path = require("path"); -let { parse } = require("./index"); +let { parse, parseOneLiner } = require("./index"); test("single quote", () => expect(parse('a "\\\'"')).toEqual([["a", "'"]])); @@ -91,3 +91,33 @@ test("with escaping", () => { data = [["\\u{3455}"]]; expect(parse(code)).toEqual(data); }); + +describe("parseOneLiner", () => { + test("simple expression", () => { + expect(parseOneLiner("add 1 2")).toEqual(["add", "1", "2"]); + }); + + test("expression with parentheses", () => { + expect(parseOneLiner("(add 1 2)")).toEqual([["add", "1", "2"]]); + }); + + test("nested expression", () => { + expect(parseOneLiner("add (mul 2 3) 4")).toEqual(["add", ["mul", "2", "3"], "4"]); + }); + + test("expression with string", () => { + expect(parseOneLiner('print "hello world"')).toEqual(["print", "hello world"]); + }); + + test("single token", () => { + expect(parseOneLiner("token")).toEqual(["token"]); + }); + + test("throws error on empty input", () => { + expect(() => parseOneLiner("")).toThrow("Expected single expression, got 0 expressions"); + }); + + test("throws error on multiple expressions", () => { + expect(() => parseOneLiner("add 1 2\nmul 3 4")).toThrow("Expected single expression, got 2 expressions"); + }); +});