From 494a9effc84ae9bcaedd8fafdd0aef8dde34a768 Mon Sep 17 00:00:00 2001 From: Kefniark Date: Sun, 16 May 2021 21:51:53 +0900 Subject: [PATCH 1/2] Work on CLI structure + Module dependency --- ako_grammar.txt | 6 +- package-lock.json | 132 +++++++++++++++++++++--- package.json | 4 +- samples/manifest.json | 9 -- samples/manifest.yaml | 6 ++ samples/math/main.ako | 3 + samples/math/manifest.json | 7 -- samples/math/manifest.yaml | 6 ++ samples/{ => src}/fibo.ako | 0 samples/{ => src}/main.ako | 3 + samples/vec/main.ako | 3 + samples/vec/manifest.json | 5 - samples/vec/manifest.yaml | 5 + src/dist/ako-cli.ts | 137 +++---------------------- src/dist/cli/cli-deps.ts | 35 +++++++ src/dist/cli/cli-new.ts | 46 +++++++++ src/dist/cli/cli-run.ts | 205 +++++++++++++++++++++++++++++++++++++ src/elements/function.ts | 39 +++---- src/helpers/folder.ts | 11 +- src/semantic.ts | 50 ++++----- 20 files changed, 502 insertions(+), 210 deletions(-) delete mode 100644 samples/manifest.json create mode 100644 samples/manifest.yaml delete mode 100644 samples/math/manifest.json create mode 100644 samples/math/manifest.yaml rename samples/{ => src}/fibo.ako (100%) rename samples/{ => src}/main.ako (87%) delete mode 100644 samples/vec/manifest.json create mode 100644 samples/vec/manifest.yaml create mode 100644 src/dist/cli/cli-deps.ts create mode 100644 src/dist/cli/cli-new.ts create mode 100644 src/dist/cli/cli-run.ts diff --git a/ako_grammar.txt b/ako_grammar.txt index 83cb767..30e1f67 100644 --- a/ako_grammar.txt +++ b/ako_grammar.txt @@ -15,8 +15,8 @@ Ako { Metadata = "##" id Term TaskDef = "task" id (Array)? Block - SkipTask = "@@" (id ".")? id "(" NamedArguments ")" - Task = "@" (id ".")? id "(" NamedArguments ")" + SkipTask = "@@" ListOf "(" NamedArguments ")" + Task = "@" ListOf "(" NamedArguments ")" NamedArguments = ListOf<(KeyValue | Expr), ","> Arguments = ListOf @@ -26,7 +26,7 @@ Ako { ExprItem = LambdaInline | MathExpr Term = Fn | bool | Var | Number | String | Array | Dictionary | Last - Fn = (id ".")? id "(" Arguments ")" + Fn = ListOf "(" Arguments ")" // Lambda = "(" ListOf ")" "=>" Block LambdaInline = "(" ListOf ")" "=>" Expr diff --git a/package-lock.json b/package-lock.json index 2ee9adf..66677e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -326,6 +326,15 @@ "strip-json-comments": "^3.1.1" }, "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, "globals": { "version": "12.4.0", "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", @@ -340,6 +349,16 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } } } }, @@ -354,6 +373,27 @@ "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + } } }, "@istanbuljs/schema": { @@ -719,13 +759,9 @@ "dev": true }, "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "array-find-index": { "version": "1.0.2", @@ -1282,10 +1318,9 @@ "dev": true }, "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" }, "commondir": { "version": "1.0.1", @@ -1714,6 +1749,15 @@ "color-convert": "^1.9.0" } }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, "cliui": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", @@ -1761,6 +1805,16 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", @@ -2167,6 +2221,15 @@ "@babel/highlight": "^7.10.4" } }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2193,6 +2256,16 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -3668,13 +3741,11 @@ "dev": true }, "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" } }, "jsesc": { @@ -3813,6 +3884,25 @@ "strip-bom": "^3.0.0" }, "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -5969,6 +6059,14 @@ "dev": true, "requires": { "commander": "^2.8.1" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + } } }, "semver": { diff --git a/package.json b/package.json index ebe1384..fcea487 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,8 @@ }, "dependencies": { "chalk": "^4.1.1", - "dateformat": "^4.5.1" + "commander": "^7.2.0", + "dateformat": "^4.5.1", + "js-yaml": "^4.1.0" } } diff --git a/samples/manifest.json b/samples/manifest.json deleted file mode 100644 index be5cfd5..0000000 --- a/samples/manifest.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "id": "Test Project", - "entry": [ - "main.ako" - ], - "deps": [ - "./math" - ] -} diff --git a/samples/manifest.yaml b/samples/manifest.yaml new file mode 100644 index 0000000..9d15035 --- /dev/null +++ b/samples/manifest.yaml @@ -0,0 +1,6 @@ +id: "Test" +entry: +- main.ako +deps: + - { scope: Math, file: ./math, version: latest } + - { scope: FS, file: ../../ako-stdlib/fs, version: latest } diff --git a/samples/math/main.ako b/samples/math/main.ako index 84d0097..478a92b 100644 --- a/samples/math/main.ako +++ b/samples/math/main.ako @@ -18,3 +18,6 @@ task test [ { name = "param", default = 1 } ] { @print("math") @Vec.main() + +cwd = fs.cwd() +@print("This is the math one {cwd}") diff --git a/samples/math/manifest.json b/samples/math/manifest.json deleted file mode 100644 index bd57494..0000000 --- a/samples/math/manifest.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "id": "Math module", - "namespace": "Math", - "deps": [ - "../vec" - ] -} diff --git a/samples/math/manifest.yaml b/samples/math/manifest.yaml new file mode 100644 index 0000000..e7de86f --- /dev/null +++ b/samples/math/manifest.yaml @@ -0,0 +1,6 @@ +id: "Math" +deps: +- { scope: Vec, file: ../vec, version: latest } +- { scope: fs, file: ../../../ako-stdlib/fs, version: latest } +paths: + code: '.' diff --git a/samples/fibo.ako b/samples/src/fibo.ako similarity index 100% rename from samples/fibo.ako rename to samples/src/fibo.ako diff --git a/samples/main.ako b/samples/src/main.ako similarity index 87% rename from samples/main.ako rename to samples/src/main.ako index 3314deb..6ff016b 100644 --- a/samples/main.ako +++ b/samples/src/main.ako @@ -17,3 +17,6 @@ task DelayMessage ["msg"] { name = @ask('What is your name ?') @DelayMessage("Hello {name} !") + +cwd = FS.cwd() +@print("This is the final {cwd}") diff --git a/samples/vec/main.ako b/samples/vec/main.ako index 6b3830a..ee097f0 100644 --- a/samples/vec/main.ako +++ b/samples/vec/main.ako @@ -6,3 +6,6 @@ for countdown in [5,4,3,2,1,0] { @print("Countdown Finish !") } } + +cwd = fs.cwd() +@print("This is the vec one {cwd}") diff --git a/samples/vec/manifest.json b/samples/vec/manifest.json deleted file mode 100644 index 7958cf3..0000000 --- a/samples/vec/manifest.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "id": "Vec module", - "namespace": "Vec", - "deps": [] -} diff --git a/samples/vec/manifest.yaml b/samples/vec/manifest.yaml new file mode 100644 index 0000000..8529143 --- /dev/null +++ b/samples/vec/manifest.yaml @@ -0,0 +1,5 @@ +id: "Vector2" +paths: + code: '.' +deps: +- { scope: fs, file: ../../../ako-stdlib/fs, version: latest } diff --git a/src/dist/ako-cli.ts b/src/dist/ako-cli.ts index 56a654b..93831bc 100644 --- a/src/dist/ako-cli.ts +++ b/src/dist/ako-cli.ts @@ -1,123 +1,14 @@ -import {getGrammar} from '../semantic' -import {Interpreter} from '../interpreter' -import fs from 'fs' -import path from 'path' -import {listAkoFiles} from '../helpers/folder' -import akoGrammar from '../../ako_grammar.txt' -import {AnalyzeInfo, Analyzer} from '../analyzer' -import {Command} from '../core' -import chalk from 'chalk' - -function loadAkoModule(vm: Interpreter, projectFolder: string, info: AnalyzeInfo[]) { - const packagePath = path.join(projectFolder, 'manifest.json') - if (!fs.existsSync(packagePath)) return - const mod = JSON.parse(fs.readFileSync(packagePath, 'utf-8')) - const namespace = mod.namespace ? mod.namespace : '' - - // load deps - if (mod.deps) { - for (const dep of mod.deps) { - loadAkoModule(vm, path.join(projectFolder, dep), info) - } - } - - // load files - const files = listAkoFiles(projectFolder) - const codes: [string, Command[]][] = [] - for (const file of files) { - const fileId = file.replace('.ako', '') - const codeTxt = fs.readFileSync(path.join(projectFolder, file), 'utf-8') - const match = grammar.match(codeTxt.toString()) - if (!match || match.failed()) { - info.push({ - level: 'error', - line: -1, - file: path.join(projectFolder, file), - message: `Critical Syntax Error`, - sample: match.message - }) - } else { - const ast = ASTBuilder(match).toAST() - const methodName = namespace ? `${namespace}.${fileId}` : fileId - - if (vm.tasks.has(methodName)) { - throw new Error(`Task Name Already Used : ${methodName}`) - } - - vm.addFile(methodName, ast) - codes.push([path.join(projectFolder, file), ast]) - } - } - - const analyzer = new Analyzer(interpreter) - for (const entry of codes) { - const errors = analyzer.validate(entry[1]) - errors.forEach((x) => (x.file = entry[0])) - info = [...info, ...errors] - } - - if (info.some((x) => x.level === 'error')) { - console.log(chalk.bold(chalk.red(`[AKO CLI] Found ${info.length} Errors in the code:`))) - for (let i = 0; i < info.length; i++) { - const num = chalk.cyanBright(`${i + 1}`) - const level = - info[i].level === 'error' ? chalk.red(`[${info[i].level.toUpperCase()}]`) : chalk.yellow(`[${info[i].level.toUpperCase()}]`) - const line = info[i].line >= 0 ? ` - line ${info[i].line + 1}` : '' - const location = chalk.green(`(Location: ${info[i].file}${line})`) - const code = - (info[i].sample_before ? info[i].sample_before : '') + - chalk.bold(info[i].sample) + - (info[i].sample_after ? info[i].sample_after : '') - console.log(`- ${level} ${num} : ${info[i].message} ${location}`) - console.log(chalk.gray(' ... ') + code.split('\n').join('\n ') + chalk.gray(' ...')) - console.log(' ') - } - process.exit(1) - } - - // execute entry point - if (mod.entry) { - for (const file of mod.entry) { - const fileId = file.replace('.ako', '') - const method = namespace ? `${namespace}.${fileId}` : fileId - const match = grammar.match(`@${method}()`) - const ast = ASTBuilder(match).toAST() - // console.log('Create Stack >', fileId) - vm.createStack(ast) - } - } -} - -// Get file content -const args = process.argv.slice(2) -let argFile = args.shift() -if (!argFile || !fs.existsSync(argFile)) throw new Error(`File does not exists : ${argFile}`) -if (fs.lstatSync(argFile).isDirectory()) argFile = path.join(argFile, 'manifest.json') - -// Parse code to AST -const {grammar, ASTBuilder} = getGrammar(akoGrammar) -const interpreter = new Interpreter() - -// Open a project -const folder = path.dirname(argFile) -const modulePath = path.join(folder, 'manifest.json') -if (fs.existsSync(modulePath)) { - const info: AnalyzeInfo[] = [] - loadAkoModule(interpreter, folder, info) -} else { - const codeTxt = fs.readFileSync(path.resolve(argFile)) - const match = grammar.match(codeTxt.toString()) - if (match.failed()) throw new Error(`Syntax in file "${argFile}", ${match.message}`) - if (!match) throw new Error(`Syntax Error with file ${argFile}`) - const ast = ASTBuilder(match).toAST() - interpreter.createStack(ast) -} - -// update interpreter with setInterval -let last = Date.now() -const inter = setInterval(() => { - const dt = Date.now() - last - interpreter.update(dt) - last = Date.now() - if (interpreter.stacks.size <= 0) clearInterval(inter) -}, 20) +import commander from 'commander' +import {makeRunCommands} from './cli/cli-run' +import {makeDepsCommands} from './cli/cli-deps' +import {makeNewCommands} from './cli/cli-new' + +// process CLI parameters +const program = new commander.Command() +program.name('ako') +program.version('0.0.15') + +program.addCommand(makeDepsCommands()) +makeRunCommands(program) +makeNewCommands(program) +program.parse(process.argv) diff --git a/src/dist/cli/cli-deps.ts b/src/dist/cli/cli-deps.ts new file mode 100644 index 0000000..cf89c3f --- /dev/null +++ b/src/dist/cli/cli-deps.ts @@ -0,0 +1,35 @@ +import commander from 'commander' + +function depsAdd(source: string) { + console.log('Deps Add', source) +} + +/** + * Manage Dependencies + */ +export function makeDepsCommands(): commander.Command { + const parent = new commander.Command('deps').description('Manage dependencies') + parent + .command('install') + .description('Install defined dependencies') + .option('-f --force', 'Force to download and install dependencies') + .action(() => { + console.log('Install') + }) + + parent.command('add ').description('Add a dependency to the module').action(depsAdd) + + parent + .command('update') + .description('Check dependencies versions') + .action(() => console.log('Deps Checks')) + // .action((source)) + + parent + .command('remove ') + .description('Delete a dependency from the module') + .action(() => console.log('Deps Delete')) + // .action((source)) + + return parent +} diff --git a/src/dist/cli/cli-new.ts b/src/dist/cli/cli-new.ts new file mode 100644 index 0000000..623c50a --- /dev/null +++ b/src/dist/cli/cli-new.ts @@ -0,0 +1,46 @@ +import commander from 'commander' +import fs from 'fs' +import path from 'path' + +export function makeNewCommands(program: commander.Command): void { + program + .command('new ') + .description('Create a new Ako Module') + .action((name: string) => { + const filepath = path.resolve('.', name) + if (fs.existsSync(filepath)) throw new Error(`Folder ${filepath} already exist`) + + fs.mkdirSync(filepath) + + fs.mkdirSync(path.join(filepath, 'src')) + fs.mkdirSync(path.join(filepath, 'assets')) + fs.writeFileSync( + path.join(filepath, 'manifest.yaml'), + `id: "${name}" +description: "My description" +entry: +- src/index.ako +` + ) + fs.writeFileSync( + path.join(filepath, 'Readme.md'), + `# ${name} + +## Description +This is a [Ako module](https://github.com/ako-lang/ako) + +## Commands +\`\`\`sh +ako install +ako run +\`\`\` +` + ) + fs.writeFileSync( + path.join(filepath, 'src', 'index.ako'), + `name = "World" +@print("Hello {name}!") +` + ) + }) +} diff --git a/src/dist/cli/cli-run.ts b/src/dist/cli/cli-run.ts new file mode 100644 index 0000000..02fac1b --- /dev/null +++ b/src/dist/cli/cli-run.ts @@ -0,0 +1,205 @@ +import commander from 'commander' +import ohm from 'ohm-js' +import {getGrammar} from '../../semantic' +import {Interpreter} from '../../interpreter' +import fs from 'fs' +import path from 'path' +import {listAkoFiles} from '../../helpers/folder' +import akoGrammar from '../../../ako_grammar.txt' +import {AnalyzeInfo, Analyzer} from '../../analyzer' +import {Command} from '../../core' +import chalk from 'chalk' +import yaml from 'js-yaml' + +function loadAkoModule( + grammar: ohm.Grammar, + ASTBuilder: ohm.Semantics, + vm: Interpreter, + projectFolder: string, + info: AnalyzeInfo[], + scope: string[] = [], + overwriteScope?: string +) { + const packagePath = path.join(projectFolder, 'manifest.yaml') + if (!fs.existsSync(packagePath)) return + const mod = yaml.load(fs.readFileSync(packagePath, 'utf-8')) + let currentScope = [] + if (scope.length > 0 || overwriteScope) { + currentScope = [...scope, overwriteScope ? overwriteScope : mod.id] + } + + const depsScope = new Map() + const localScope = new Map() + + // load deps + if (mod.deps) { + for (const dep of mod.deps) { + const depScope = 'scope' in dep ? dep.scope : undefined + if ('file' in dep) { + loadAkoModule(grammar, ASTBuilder, vm, path.join(projectFolder, dep.file), info, currentScope, depScope) + } + depsScope.set(depScope, [...currentScope, depScope].join('.')) + } + } + + const cur = path.relative(__dirname, path.resolve(projectFolder)) + + if (mod.functions) { + if (!Array.isArray(mod.functions)) mod.functions = [mod.functions] + for (const dep of mod.functions) { + if (!fs.existsSync(path.join(projectFolder, dep))) { + console.warn(`Cant open function ${path.join(projectFolder, dep)}. Please Check your manifest file.`) + continue + } + // eslint-disable-next-line @typescript-eslint/no-var-requires + const functions = require('./' + path.join(cur, dep).replace(/\\/g, '/').replace('.js', '')) + Object.keys(functions).forEach((name) => { + vm.registerFunction([...currentScope, name].join('.'), functions[name]) + localScope.set(name, [...currentScope, name].join('.')) + }) + } + } + + if (mod.commands) { + if (!Array.isArray(mod.commands)) mod.commands = [mod.commands] + for (const dep of mod.commands) { + if (!fs.existsSync(path.join(projectFolder, dep))) { + console.warn(`Cant open function ${path.join(projectFolder, dep)}. Please Check your manifest file.`) + continue + } + // eslint-disable-next-line @typescript-eslint/no-var-requires + const tasks = require('./' + path.join(cur, dep).replace(/\\/g, '/').replace('.js', '')) + Object.keys(tasks).forEach((name) => { + vm.registerTask([...currentScope, name].join('.'), tasks[name]) + localScope.set(name, [...currentScope, name].join('.')) + }) + } + } + + const paths = Object.assign({assets: './assets', code: './src'}, mod.paths) + + // load files + const files = listAkoFiles(path.join(projectFolder, paths.code)).map((x) => + x.replace(path.join(projectFolder, paths.code) + '/', '').replace(/\\/g, '/') + ) + + const codes: [string, Command[]][] = [] + for (const file of files) { + const fileId = path.basename(file).replace('.ako', '') + const codeTxt = fs.readFileSync(file, 'utf-8') + const match = grammar.match(codeTxt.toString()) + if (!match || match.failed()) { + info.push({ + level: 'error', + line: -1, + file: file, + message: `Critical Syntax Error`, + sample: match.message + }) + } else { + const ast = ASTBuilder(match).toAST() + const methodName = [...currentScope, fileId].join('.') + + if (vm.tasks.has(methodName)) { + throw new Error(`Task Name Already Used : ${methodName}`) + } + + vm.addFile(methodName, ast) + codes.push([file, ast]) + } + } + + const analyzer = new Analyzer(vm) + for (const entry of codes) { + // rewrite dependencies + const nodes = analyzer.getNodes(entry[1], (x) => x.type === 'Task' || x.type === 'Function') + const keyVals = [...depsScope] + nodes.forEach((x: any) => { + const find = keyVals.find((y) => x.name.startsWith(`${y[0]}.`)) + if (find) { + x.name = `${find[1]}.${x.name.replace(find[0] + '.', '')}` + } else if (localScope.has(x.name)) { + x.name = localScope.get(x.name) + } + }) + + // validates + const errors = analyzer.validate(entry[1]) + errors.forEach((x) => (x.file = entry[0])) + info = [...info, ...errors] + } + + if (info.some((x) => x.level === 'error')) { + console.log(chalk.bold(chalk.red(`[AKO CLI] Found ${info.length} Errors in the code:`))) + for (let i = 0; i < info.length; i++) { + const num = chalk.cyanBright(`${i + 1}`) + const level = + info[i].level === 'error' ? chalk.red(`[${info[i].level.toUpperCase()}]`) : chalk.yellow(`[${info[i].level.toUpperCase()}]`) + const line = info[i].line >= 0 ? ` - line ${info[i].line + 1}` : '' + const location = chalk.green(`(Location: ${info[i].file}${line})`) + const code = + (info[i].sample_before ? info[i].sample_before : '') + + chalk.bold(info[i].sample) + + (info[i].sample_after ? info[i].sample_after : '') + console.log(`- ${level} ${num} : ${info[i].message} ${location}`) + console.log(chalk.gray(' ... ') + code.split('\n').join('\n ') + chalk.gray(' ...')) + console.log(' ') + } + process.exit(1) + } + + // execute entry point + const entry = Object.assign({entry: ''}, mod) + if (!Array.isArray(entry.entry)) entry.entry = [entry.entry] + for (const file of entry.entry) { + if (!file) continue + const fileId = path.basename(file).replace('.ako', '') + const method = [...currentScope, fileId].join('.') + const match = grammar.match(`@${method}()`) + const ast = ASTBuilder(match).toAST() + vm.createStack(ast) + } +} + +/** + * Run Module + */ +export function makeRunCommands(program: commander.Command): void { + program + .command('run [source]') + .description('Execute an ako script') + .action((source?: string) => { + source = source !== undefined ? source : '.' + if (!source || !fs.existsSync(source)) throw new Error(`File does not exists : ${source}`) + if (fs.lstatSync(source).isDirectory()) source = path.join(source, 'manifest.yaml') + + // Parse code to AST + const {grammar, ASTBuilder} = getGrammar(akoGrammar) + const interpreter = new Interpreter() + + // Open a project + const folder = path.dirname(source) + const modulePath = path.join(folder, 'manifest.yaml') + if (fs.existsSync(modulePath)) { + const info: AnalyzeInfo[] = [] + loadAkoModule(grammar, ASTBuilder, interpreter, folder, info) + } else { + const codeTxt = fs.readFileSync(path.resolve(source)) + const match = grammar.match(codeTxt.toString()) + if (!match || match.failed()) { + throw new Error(`Syntax in file "${source}", ${match.message}`) + } + const ast = ASTBuilder(match).toAST() + interpreter.createStack(ast) + } + + // update interpreter with setInterval + let last = Date.now() + const inter = setInterval(() => { + const dt = Date.now() - last + interpreter.update(dt) + last = Date.now() + if (interpreter.stacks.size <= 0) clearInterval(inter) + }, 20) + }) +} diff --git a/src/elements/function.ts b/src/elements/function.ts index aaea60c..e532252 100644 --- a/src/elements/function.ts +++ b/src/elements/function.ts @@ -57,16 +57,19 @@ const evalArgs = (ctx, args) => { } export const Task = { - create: (namespace, name, args, skip) => { - return {type: 'Task', namespace, name, args, skip} + create: (name, args, skip) => { + return { + type: 'Task', + name: name + .map((x) => x.value.trim()) + .filter((x) => !!x) + .join('.'), + args, + skip + } }, name: (ctx, entry) => { - let fn = ctx.vm.evaluate(ctx, entry.name) - if (entry.namespace && entry.namespace.length > 0) { - const namespace = ctx.vm.evaluate(ctx, entry.namespace[0], true) - fn = `${namespace}.${fn}` - } - return fn + return ctx.vm.evaluate(ctx, entry.name) }, execute: (ctx, entry, entryData, timeRemains) => { if (entry.skip) { @@ -126,18 +129,18 @@ export const TaskDef = { } export const Function = { - create: (namespace, name, args) => { - return {type: 'Function', namespace, name, args} + create: (name, args) => { + return { + type: 'Function', + name: name + .map((x) => x.value) + .filter((x) => !!x.trim()) + .join('.'), + args + } }, name: (ctx: Context, entry) => { - let fn = ctx.vm.evaluate(ctx, entry.name) - if (entry.namespace) { - const entries = entry.namespace.map((x) => ctx.vm.evaluate(ctx, x)) - if (entries.length > 0) { - fn = `${entries.join(',')}.${ctx.vm.evaluate(ctx, entry.name)}` - } - } - return fn + return ctx.vm.evaluate(ctx, entry.name) }, evaluate: (ctx: Context, entry) => { const name = Function.name(ctx, entry) diff --git a/src/helpers/folder.ts b/src/helpers/folder.ts index 73a8396..808dcd9 100644 --- a/src/helpers/folder.ts +++ b/src/helpers/folder.ts @@ -1,6 +1,13 @@ import fs from 'fs' +import path from 'path' -export function listAkoFiles(folder: string) { - const files = fs.readdirSync(folder) +export function listAkoFiles(folder: string): string[] { + let files = fs.readdirSync(folder) + files = files.map((x) => path.join(folder, x)) + for (const file of files) { + if (fs.statSync(file).isDirectory()) { + files = [...files, ...listAkoFiles(file)] as string[] + } + } return files.filter((x) => x.endsWith('.ako')) } diff --git a/src/semantic.ts b/src/semantic.ts index b2cbd7d..d425d6d 100644 --- a/src/semantic.ts +++ b/src/semantic.ts @@ -6,13 +6,13 @@ export function getGrammar(akoGrammar: string) { const semantics = grammar.createSemantics() semantics.addOperation('calc', { - int: function (a) { + int: function (_) { return parseInt(this.sourceString, 10) }, - float: function (a, b, c) { + float: function (_, __, ___) { return parseFloat(this.sourceString) }, - hex: function (a, b) { + hex: function (_, __) { return Number(`0x${this.sourceString.slice(1)}`) } }) @@ -35,20 +35,20 @@ export function getGrammar(akoGrammar: string) { const ASTBuilder = semantics.addOperation('toAST', { // Operator - EqExpr_eq: (a, b, c) => debugWrapper(a, c, AkoElement.Operator.create('==', a.toAST(), c.toAST())), - EqExpr_neq: (a, b, c) => debugWrapper(a, c, AkoElement.Operator.create('!=', a.toAST(), c.toAST())), - EqExpr_lt: (a, b, c) => debugWrapper(a, c, AkoElement.Operator.create('<', a.toAST(), c.toAST())), - EqExpr_lte: (a, b, c) => debugWrapper(a, c, AkoElement.Operator.create('<=', a.toAST(), c.toAST())), - EqExpr_gt: (a, b, c) => debugWrapper(a, c, AkoElement.Operator.create('>', a.toAST(), c.toAST())), - EqExpr_gte: (a, b, c) => debugWrapper(a, c, AkoElement.Operator.create('>=', a.toAST(), c.toAST())), - BinExpr_and: (a, b, c) => debugWrapper(a, c, AkoElement.Operator.create('and', a.toAST(), c.toAST())), - BinExpr_or: (a, b, c) => debugWrapper(a, c, AkoElement.Operator.create('or', a.toAST(), c.toAST())), + EqExpr_eq: (a, _, c) => debugWrapper(a, c, AkoElement.Operator.create('==', a.toAST(), c.toAST())), + EqExpr_neq: (a, _, c) => debugWrapper(a, c, AkoElement.Operator.create('!=', a.toAST(), c.toAST())), + EqExpr_lt: (a, _, c) => debugWrapper(a, c, AkoElement.Operator.create('<', a.toAST(), c.toAST())), + EqExpr_lte: (a, _, c) => debugWrapper(a, c, AkoElement.Operator.create('<=', a.toAST(), c.toAST())), + EqExpr_gt: (a, _, c) => debugWrapper(a, c, AkoElement.Operator.create('>', a.toAST(), c.toAST())), + EqExpr_gte: (a, _, c) => debugWrapper(a, c, AkoElement.Operator.create('>=', a.toAST(), c.toAST())), + BinExpr_and: (a, _, c) => debugWrapper(a, c, AkoElement.Operator.create('and', a.toAST(), c.toAST())), + BinExpr_or: (a, _, c) => debugWrapper(a, c, AkoElement.Operator.create('or', a.toAST(), c.toAST())), - AddExpr_plus: (a, b, c) => debugWrapper(a, c, AkoElement.MathOp.create('+', a.toAST(), c.toAST())), - AddExpr_minus: (a, b, c) => debugWrapper(a, c, AkoElement.MathOp.create('-', a.toAST(), c.toAST())), - MulExpr_times: (a, b, c) => debugWrapper(a, c, AkoElement.MathOp.create('*', a.toAST(), c.toAST())), - MulExpr_divide: (a, b, c) => debugWrapper(a, c, AkoElement.MathOp.create('/', a.toAST(), c.toAST())), - MulExpr_mod: (a, b, c) => debugWrapper(a, c, AkoElement.MathOp.create('%', a.toAST(), c.toAST())), + AddExpr_plus: (a, _, c) => debugWrapper(a, c, AkoElement.MathOp.create('+', a.toAST(), c.toAST())), + AddExpr_minus: (a, _, c) => debugWrapper(a, c, AkoElement.MathOp.create('-', a.toAST(), c.toAST())), + MulExpr_times: (a, _, c) => debugWrapper(a, c, AkoElement.MathOp.create('*', a.toAST(), c.toAST())), + MulExpr_divide: (a, _, c) => debugWrapper(a, c, AkoElement.MathOp.create('/', a.toAST(), c.toAST())), + MulExpr_mod: (a, _, c) => debugWrapper(a, c, AkoElement.MathOp.create('%', a.toAST(), c.toAST())), // Type Number: (a) => debugWrapper(a, a, AkoElement.Number.create(a.calc())), @@ -74,13 +74,13 @@ export function getGrammar(akoGrammar: string) { KeyValue: (a, _, c) => debugWrapper(a, c, AkoElement.KeyValue.create(a.toAST(), c.toAST())), // - Task: (a, b, c, d, e, f, g) => debugWrapper(a, g, AkoElement.Task.create(b.toAST(), d.toAST(), f.toAST(), false)), - SkipTask: (a, b, c, d, e, f, g) => debugWrapper(a, g, AkoElement.Task.create(b.toAST(), d.toAST(), f.toAST(), true)), + Task: (a, b, _, f, g) => debugWrapper(a, g, AkoElement.Task.create(b.asIteration().toAST(), f.toAST(), false)), + SkipTask: (a, b, _, f, g) => debugWrapper(a, g, AkoElement.Task.create(b.asIteration().toAST(), f.toAST(), true)), TaskDef: (a, b, c, d) => debugWrapper(a, d, AkoElement.TaskDef.create(b.toAST(), c.toAST(), d.toAST())), - Fn: (a, b, c, d, e, f) => debugWrapper(a, f, AkoElement.Function.create(a.toAST(), c.toAST(), e.toAST())), + Fn: (a, _, e, f) => debugWrapper(a, f, AkoElement.Function.create(a.toAST(), e.toAST())), Arguments: (a) => debugWrapper(a, a, a.asIteration().toAST()), ListOf: (a) => debugWrapper(a, a, a.asIteration().toAST()), - Pipe: (a, b, c) => debugWrapper(a, c, AkoElement.Pipe.create(a.toAST(), c.toAST())), + Pipe: (a, _, c) => debugWrapper(a, c, AkoElement.Pipe.create(a.toAST(), c.toAST())), Metadata: (a, b, c) => debugWrapper(a, c, AkoElement.Metadata.create(b.toAST(), c.toAST())), // Assign @@ -94,13 +94,13 @@ export function getGrammar(akoGrammar: string) { // Loop Infinite: (a, b) => debugWrapper(a, b, AkoElement.LoopInfinite.create(b.toAST())), While: (a, b, c) => debugWrapper(a, c, AkoElement.LoopWhile.create(b.toAST(), c.toAST())), - Foreach: (a, b, c, d, e, f, g) => debugWrapper(a, g, AkoElement.LoopFor.create(b.toAST(), d.toAST(), f.toAST(), g.toAST())), + Foreach: (a, b, _, d, __, f, g) => debugWrapper(a, g, AkoElement.LoopFor.create(b.toAST(), d.toAST(), f.toAST(), g.toAST())), Block: (a, b, c) => debugWrapper(a, c, AkoElement.Block.create(b.toAST())), // Lambda: (a, b, c, d, e) => { // console.log(e.sourceString) // return AkoElement.Lambda.create(b.toAST(), e.toAST()) // }, - LambdaInline: (a, b, c, d, e) => debugWrapper(a, e, AkoElement.Lambda.create(b.toAST(), e.toAST())), + LambdaInline: (a, b, _, __, e) => debugWrapper(a, e, AkoElement.Lambda.create(b.toAST(), e.toAST())), Continue: (a) => debugWrapper(a, a, AkoElement.Continue.create()), Return: (a, b) => debugWrapper(a, b, AkoElement.Return.create(b.toAST())), @@ -108,9 +108,9 @@ export function getGrammar(akoGrammar: string) { comment: (a) => debugWrapper(a, a, AkoElement.Comment.create(a.sourceString)), id: (a) => debugWrapper(a, a, AkoElement.String.create(a.sourceString)), Var_single: (a) => debugWrapper(a, a, AkoElement.Symbol.create(a.sourceString)), - Var_select: (a, b, c) => debugWrapper(a, c, AkoElement.SymbolSelect.create(a.toAST(), c.toAST())), - Var_range: (a, b, c, d, e, f) => debugWrapper(a, f, AkoElement.SymbolRange.create(a.toAST(), c.toAST(), e.toAST())), - Var_subscript: (a, b, c, d) => debugWrapper(a, d, AkoElement.SymbolSub.create(a.toAST(), c.toAST())), + Var_select: (a, _, c) => debugWrapper(a, c, AkoElement.SymbolSelect.create(a.toAST(), c.toAST())), + Var_range: (a, _, c, __, e, f) => debugWrapper(a, f, AkoElement.SymbolRange.create(a.toAST(), c.toAST(), e.toAST())), + Var_subscript: (a, _, c, d) => debugWrapper(a, d, AkoElement.SymbolSub.create(a.toAST(), c.toAST())), Last: (a) => debugWrapper(a, a, AkoElement.SymbolLast.create()) }) From 5c6a59ba0a59a68ee64a5a0574205631259f36b7 Mon Sep 17 00:00:00 2001 From: Kefniark Date: Mon, 17 May 2021 20:57:38 +0900 Subject: [PATCH 2/2] Improve cli dependency commands + update doc --- README.md | 6 +-- _sidebar.md | 2 + docs/cli_index.md | 52 ++++++++++++++++++ docs/getting_started.md | 4 +- docs/module_index.md | 40 ++++++++++++++ package-lock.json | 71 ++++++++++++++++++++++-- package.json | 1 + samples/.gitignore | 1 + samples/manifest.yaml | 13 +++-- samples/math/main.ako | 3 -- samples/math/manifest.yaml | 4 +- samples/src/main.ako | 3 -- samples/vec/main.ako | 3 -- samples/vec/manifest.yaml | 2 +- src/analyzer.ts | 1 - src/core.ts | 11 ++++ src/dist/cli/cli-deps.ts | 107 ++++++++++++++++++++++++++++++++----- src/dist/cli/cli-new.ts | 10 ++-- src/dist/cli/cli-run.ts | 27 +++++++--- src/dist/cli/helpers.ts | 16 ++++++ src/interpreter.ts | 1 - src/semantic.ts | 4 -- 22 files changed, 328 insertions(+), 54 deletions(-) create mode 100644 docs/cli_index.md create mode 100644 docs/module_index.md create mode 100644 samples/.gitignore create mode 100644 src/dist/cli/helpers.ts diff --git a/README.md b/README.md index 10258c5..d420b59 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Ako tries to fill the gap by providing at the same time: npm install -g ako-lang # Execute -ako ./test.ako +ako run ./test.ako ``` ## Standalone Executable @@ -68,7 +68,7 @@ You can directly use standalone executable of the interpreter : [Release page](h It's compiled for **Windows**, **Mac** and **Linux** and once downloaded, you can use it to run Ako scripts ```sh -./ako.exe test.ako +./ako.exe run test.ako ``` ## CDN @@ -93,7 +93,7 @@ npm install # install deps npm run build npm link # link the local file globally -ako ./samples/ # use the interpreter +ako run ./samples/ # use the interpreter ``` ## Other diff --git a/_sidebar.md b/_sidebar.md index 2dcaecc..55b1dff 100644 --- a/_sidebar.md +++ b/_sidebar.md @@ -1,5 +1,7 @@ * [Ako](/) * [Getting Started](./docs/getting_started.md) + * [CLI](./docs/cli_index.md) + * [Module](./docs/module_index.md) * [Grammar](./docs/grammar_index.md) * [Basic Types](./docs/grammar/grammar_basic.md) * [Conditional](./docs/grammar/grammar_cond.md) diff --git a/docs/cli_index.md b/docs/cli_index.md new file mode 100644 index 0000000..df0f109 --- /dev/null +++ b/docs/cli_index.md @@ -0,0 +1,52 @@ +# AKO CLI + +## Create a new module + +This will create a new module folder structure with this name + +``` +ako new [name of your module] +``` + +## Run some code + +Execute a script or a module + +```bash +ako run ./file.ako + +# or a manifest +ako run ./manifest.yaml + +# or in a project +ako run +``` + +# Manage dependencies + +## Install dependencies +Install dependencies declared in the manifest file +```bash +ako deps install +``` + +## Add dependency +Add a dependency to the manifest, download and unzip the necessary files +```bash +ako deps add [path] +``` +### Supported path: +* Filepath: ```ako deps add ./path/on/your/system``` +* Github: ```ako deps add github_organization/repo``` + * with branch: ```ako deps add github_organization/repo#develop``` + * or tags: ```ako deps add github_organization/repo#v1.0.0``` +* Gitlab: ```ako deps add gitlab:organization/repo``` + +### Options: +* -p, --path : If needed, can use a subfolder of a repository +* -s, --scope : Name used to expose this module in your ako code + +## Remove dependency +```bash +ako deps remove [path] +``` diff --git a/docs/getting_started.md b/docs/getting_started.md index ae17d1f..e9beb0d 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -7,7 +7,7 @@ npm install -g ako-lang # Execute -ako ./test.ako +ako run ./test.ako ``` ## Standalone Executable @@ -16,7 +16,7 @@ You can directly use standalone executable of the interpreter : [Release page](h It's compiled for **Windows**, **Mac** and **Linux** and once downloaded, you can use it to run Ako scripts ```sh -./ako.exe test.ako +./ako.exe run test.ako ``` ## CDN diff --git a/docs/module_index.md b/docs/module_index.md new file mode 100644 index 0000000..11ef38f --- /dev/null +++ b/docs/module_index.md @@ -0,0 +1,40 @@ +# Module + +## Description +Ako encourage reusability by organizing code in standalone modules. + +Each module contains: +* Ako scripts +* Assets +* Native function/tasks + +All the configuration is all provided by the `manifest.yaml` at the root + +## Structure +the recommended file structure is +* `manifest.yaml` : Module manifest +* `./src/` : Ako code folder +* `./assets/` : Assets that can be required by Ako code +* `./lib/` : Native function or task implementation + +it can be customized in manifest + +Look the [CLI dependencies](./cli_index.md) if you want to want to create module with `ako new [name]` + +## Dependencies +Any module can load 3rd party code by declaring dependencies. Those dependencies can be loaded localy on your file system, or downloaded from git services like github/gitlab. + +Look the [CLI dependencies](./cli_index.md) if you want to add a dependency to your project. + +### Example: +```yaml +id: Test +entry: + - main.ako +deps: + - url: ./path/module + scope: MODULE +``` +This module under `./path/module` will be loaded in a dedicated scope `MODULE.`and you can call any of his task or function. `@MODULE.main()` + +By default, the scope is the `id` of this dependency, but it can be renamed to avoid conflict diff --git a/package-lock.json b/package-lock.json index 66677e2..b2b62b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -793,6 +793,14 @@ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, + "axios": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", + "requires": { + "follow-redirects": "^1.10.0" + } + }, "babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", @@ -1221,6 +1229,11 @@ "readdirp": "~3.5.0" } }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, "ci-info": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", @@ -2614,6 +2627,11 @@ "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", "dev": true }, + "follow-redirects": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz", + "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==" + }, "foreground-child": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", @@ -2700,6 +2718,14 @@ "universalify": "^0.1.0" } }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2784,6 +2810,15 @@ "pump": "^3.0.0" } }, + "gitly": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/gitly/-/gitly-2.1.0.tgz", + "integrity": "sha512-0TL0ZGT9bdmfa+dylBpwp1ip3FQ+CQsINM4Ih+2Z+XKkDasOWSk7MborpRPsdYnKO/1LuC5HoGQg/JAVIwb0qg==", + "requires": { + "axios": "^0.21.1", + "tar": "^6.1.0" + } + }, "giturl": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/giturl/-/giturl-1.0.1.tgz", @@ -4163,11 +4198,27 @@ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, + "minipass": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "requires": { + "yallist": "^4.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" }, "mocha": { "version": "8.4.0", @@ -6409,6 +6460,19 @@ "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==", "dev": true }, + "tar": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz", + "integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, "tar-stream": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", @@ -7026,8 +7090,7 @@ "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yargs": { "version": "15.4.1", diff --git a/package.json b/package.json index fcea487..0a16981 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "chalk": "^4.1.1", "commander": "^7.2.0", "dateformat": "^4.5.1", + "gitly": "^2.1.0", "js-yaml": "^4.1.0" } } diff --git a/samples/.gitignore b/samples/.gitignore new file mode 100644 index 0000000..77b1337 --- /dev/null +++ b/samples/.gitignore @@ -0,0 +1 @@ +.ako/ diff --git a/samples/manifest.yaml b/samples/manifest.yaml index 9d15035..6fe238a 100644 --- a/samples/manifest.yaml +++ b/samples/manifest.yaml @@ -1,6 +1,11 @@ -id: "Test" +id: Test entry: -- main.ako + - main.ako +paths: + code: 'src' deps: - - { scope: Math, file: ./math, version: latest } - - { scope: FS, file: ../../ako-stdlib/fs, version: latest } + - url: ./math + scope: Math + - url: github:ako-lang/ako-libraries#develop + path: fs + scope: FS diff --git a/samples/math/main.ako b/samples/math/main.ako index 478a92b..84d0097 100644 --- a/samples/math/main.ako +++ b/samples/math/main.ako @@ -18,6 +18,3 @@ task test [ { name = "param", default = 1 } ] { @print("math") @Vec.main() - -cwd = fs.cwd() -@print("This is the math one {cwd}") diff --git a/samples/math/manifest.yaml b/samples/math/manifest.yaml index e7de86f..2811683 100644 --- a/samples/math/manifest.yaml +++ b/samples/math/manifest.yaml @@ -1,6 +1,6 @@ id: "Math" deps: -- { scope: Vec, file: ../vec, version: latest } -- { scope: fs, file: ../../../ako-stdlib/fs, version: latest } +- { scope: Vec, url: ../vec, version: latest } +- { scope: fs, url: ../../../ako-stdlib/fs, version: latest } paths: code: '.' diff --git a/samples/src/main.ako b/samples/src/main.ako index 6ff016b..3314deb 100644 --- a/samples/src/main.ako +++ b/samples/src/main.ako @@ -17,6 +17,3 @@ task DelayMessage ["msg"] { name = @ask('What is your name ?') @DelayMessage("Hello {name} !") - -cwd = FS.cwd() -@print("This is the final {cwd}") diff --git a/samples/vec/main.ako b/samples/vec/main.ako index ee097f0..6b3830a 100644 --- a/samples/vec/main.ako +++ b/samples/vec/main.ako @@ -6,6 +6,3 @@ for countdown in [5,4,3,2,1,0] { @print("Countdown Finish !") } } - -cwd = fs.cwd() -@print("This is the vec one {cwd}") diff --git a/samples/vec/manifest.yaml b/samples/vec/manifest.yaml index 8529143..983f650 100644 --- a/samples/vec/manifest.yaml +++ b/samples/vec/manifest.yaml @@ -2,4 +2,4 @@ id: "Vector2" paths: code: '.' deps: -- { scope: fs, file: ../../../ako-stdlib/fs, version: latest } +- { scope: fs, url: github:ako-lang/ako-libraries#develop, path: 'fs' } diff --git a/src/analyzer.ts b/src/analyzer.ts index 36233ae..af0f0aa 100644 --- a/src/analyzer.ts +++ b/src/analyzer.ts @@ -84,7 +84,6 @@ export class Analyzer { const key = this.interpreter.evaluate(ctx, variable, false) if (!variables.has(key.value)) { const val = variable as CommandDebug - // console.log(variable, val) info.push( Object.assign( {}, diff --git a/src/core.ts b/src/core.ts index 6a646c5..0b3c5cb 100644 --- a/src/core.ts +++ b/src/core.ts @@ -61,3 +61,14 @@ export function isString(val: any): boolean { export function isObject(val: any): boolean { return Object.prototype.toString.call(val) === '[object Object]' } + +export function getModulePath(dep: {url: string}): string { + let name = dep.url.replace(/\\/g, '/').replace(/\//g, '-') + let version = 'latest' + if (name.indexOf(':') != -1) name = name.substring(name.indexOf(':') + 1) + if (name.indexOf('#') != -1) { + version = name.substring(name.indexOf('#') + 1) + name = name.substring(0, name.indexOf('#')) + } + return `./.ako/deps/${name}@${version}/` +} diff --git a/src/dist/cli/cli-deps.ts b/src/dist/cli/cli-deps.ts index cf89c3f..a52d382 100644 --- a/src/dist/cli/cli-deps.ts +++ b/src/dist/cli/cli-deps.ts @@ -1,7 +1,71 @@ import commander from 'commander' +import fs from 'fs' +import path from 'path' +import yaml from 'js-yaml' +import gitly from 'gitly' +import {getModulePath} from '../../core' +import {parseDep} from './helpers' -function depsAdd(source: string) { - console.log('Deps Add', source) +async function depsAdd(manifest: string, data: any) { + const mod = yaml.load(fs.readFileSync(manifest, 'utf-8')) + const entryData = parseDep(data) + + let edited = false + if (!mod.deps) mod.deps = [] + const entry = mod.deps.find((x) => parseDep(x).url === entryData.url) + if (entry) { + Object.assign(entry, entryData) + edited = true + } + if (!edited) mod.deps.push(entryData) + + // yaml + fs.writeFileSync(manifest, yaml.dump(mod)) + await install(manifest) + const entryDep = mod.deps.find((x) => parseDep(x).url === entryData.url) + if (entryDep && !entryDep.scope) { + let localPath = entryDep.url + if (!entryDep.url.startsWith('.')) { + localPath = getModulePath(entryDep) + } + if ('path' in entryDep) localPath = path.join(localPath, entryDep.path) + localPath = path.join(localPath, 'manifest.yaml') + const depmod = yaml.load(fs.readFileSync(localPath, 'utf-8')) + entryDep.scope = depmod.id + fs.writeFileSync(manifest, yaml.dump(mod)) + } +} + +async function depsRemove(manifest: string, url: string) { + const mod = yaml.load(fs.readFileSync(manifest, 'utf-8')) + const entry = parseDep(url) + + if (!mod.deps) mod.deps = [] + mod.deps = mod.deps.filter((x) => x.url === entry.url || x.url.includes(entry.url)) + + // yaml + fs.writeFileSync(manifest, yaml.dump(mod)) +} + +async function install(manifestPath: string) { + const mod = yaml.load(fs.readFileSync(manifestPath, 'utf-8')) + for (const dep of mod.deps) { + const dependency = parseDep(dep) + if (!('url' in dependency)) continue + if (dependency.url.startsWith('.')) { + const libpath = path.join(path.dirname(manifestPath), dependency.url) + const targetManifest = path.join(libpath, 'path' in dependency ? dependency.path : '', 'manifest.yaml') + if (fs.existsSync(targetManifest)) await install(targetManifest) + } else { + const libpath = getModulePath(dependency) + const targetManifest = path.join(libpath, 'path' in dependency ? dependency.path : '', 'manifest.yaml') + if (!fs.existsSync(targetManifest)) { + const res = await gitly(dependency.url, libpath, {}) + if (!res || !res[0]) console.warn('Cannot install', dependency.url) + } + if (fs.existsSync(targetManifest)) await install(targetManifest) + } + } } /** @@ -12,24 +76,41 @@ export function makeDepsCommands(): commander.Command { parent .command('install') .description('Install defined dependencies') - .option('-f --force', 'Force to download and install dependencies') - .action(() => { - console.log('Install') + .option('-f, --force', 'Force to download and install dependencies') + .action(async () => { + console.log('* Dependency Install') + const source = path.join('.', 'manifest.yaml') + if (!source || !fs.existsSync(source)) throw new Error(`File does not exists : ${source}`) + await install(source) }) - parent.command('add ').description('Add a dependency to the module').action(depsAdd) - parent - .command('update') - .description('Check dependencies versions') - .action(() => console.log('Deps Checks')) - // .action((source)) + .command('add ') + .description( + `Add a dependency to the module + -> ako deps add ../../my-lib + -> ako deps add github:group/repo#branch + -> ako deps add gitlab:group/repo#branch --scope LIB --path myfolder/ako/ + -> ako deps add LIB=http://mydomain.com/thisismyrepo.git` + ) + .option('-s, --scope ') + .option('-p, --path ') + .action(async (name: string, args: any) => { + console.log('* Dependency Add -> ', parseDep(Object.assign({url: name}, args))) + const source = path.join('.', 'manifest.yaml') + if (!source || !fs.existsSync(source)) throw new Error(`File does not exists : ${source}`) + await depsAdd(source, Object.assign({url: name}, args)) + }) parent .command('remove ') .description('Delete a dependency from the module') - .action(() => console.log('Deps Delete')) - // .action((source)) + .action(async (name: string) => { + console.log('* Dependency Remove') + const source = path.join('.', 'manifest.yaml') + if (!source || !fs.existsSync(source)) throw new Error(`File does not exists : ${source}`) + await depsRemove(source, name) + }) return parent } diff --git a/src/dist/cli/cli-new.ts b/src/dist/cli/cli-new.ts index 623c50a..2eb75bf 100644 --- a/src/dist/cli/cli-new.ts +++ b/src/dist/cli/cli-new.ts @@ -18,8 +18,13 @@ export function makeNewCommands(program: commander.Command): void { path.join(filepath, 'manifest.yaml'), `id: "${name}" description: "My description" -entry: -- src/index.ako +entry: src/index.ako +deps: [] +` + ) + fs.writeFileSync( + path.join(filepath, '.gitignore'), + `.ako/ ` ) fs.writeFileSync( @@ -31,7 +36,6 @@ This is a [Ako module](https://github.com/ako-lang/ako) ## Commands \`\`\`sh -ako install ako run \`\`\` ` diff --git a/src/dist/cli/cli-run.ts b/src/dist/cli/cli-run.ts index 02fac1b..43b12d4 100644 --- a/src/dist/cli/cli-run.ts +++ b/src/dist/cli/cli-run.ts @@ -7,9 +7,10 @@ import path from 'path' import {listAkoFiles} from '../../helpers/folder' import akoGrammar from '../../../ako_grammar.txt' import {AnalyzeInfo, Analyzer} from '../../analyzer' -import {Command} from '../../core' +import {Command, getModulePath} from '../../core' import chalk from 'chalk' import yaml from 'js-yaml' +import {parseDep} from './helpers' function loadAkoModule( grammar: ohm.Grammar, @@ -33,10 +34,16 @@ function loadAkoModule( // load deps if (mod.deps) { - for (const dep of mod.deps) { + for (const dependency of mod.deps) { + const dep = parseDep(dependency) const depScope = 'scope' in dep ? dep.scope : undefined - if ('file' in dep) { - loadAkoModule(grammar, ASTBuilder, vm, path.join(projectFolder, dep.file), info, currentScope, depScope) + if (!('url' in dependency)) continue + if (dep.url.startsWith('.')) { + loadAkoModule(grammar, ASTBuilder, vm, path.join(projectFolder, dep.url), info, currentScope, depScope) + } else { + let localPath = getModulePath(dep) + if ('path' in dep) localPath = path.join(localPath, dep.path) + loadAkoModule(grammar, ASTBuilder, vm, localPath, info, currentScope, depScope) } depsScope.set(depScope, [...currentScope, depScope].join('.')) } @@ -179,11 +186,13 @@ export function makeRunCommands(program: commander.Command): void { // Open a project const folder = path.dirname(source) - const modulePath = path.join(folder, 'manifest.yaml') + const target = path.relative('.', folder) + if (source && target) process.chdir(target) + const modulePath = path.join('.', 'manifest.yaml') if (fs.existsSync(modulePath)) { const info: AnalyzeInfo[] = [] - loadAkoModule(grammar, ASTBuilder, interpreter, folder, info) - } else { + loadAkoModule(grammar, ASTBuilder, interpreter, '.', info) + } else if (fs.existsSync(source)) { const codeTxt = fs.readFileSync(path.resolve(source)) const match = grammar.match(codeTxt.toString()) if (!match || match.failed()) { @@ -191,6 +200,10 @@ export function makeRunCommands(program: commander.Command): void { } const ast = ASTBuilder(match).toAST() interpreter.createStack(ast) + } else { + console.log(chalk.bold(chalk.red(`[AKO CLI] Error ! Cannot run this code, this is not a valid Ako Module.`))) + process.exit(1) + return } // update interpreter with setInterval diff --git a/src/dist/cli/helpers.ts b/src/dist/cli/helpers.ts new file mode 100644 index 0000000..9219ec6 --- /dev/null +++ b/src/dist/cli/helpers.ts @@ -0,0 +1,16 @@ +import {isString} from '../../core' + +export function parseDep(dep: any) { + if (isString(dep)) { + if (dep.indexOf('=') !== -1) { + const val = dep.split('=') + return {scope: dep[0], url: val} + } + return {url: dep} + } else if (dep.url && dep.url.indexOf('=') !== -1) { + const val = dep.url.split('=') + dep.scope = val[0] + dep.url = val[1] + } + return dep +} diff --git a/src/interpreter.ts b/src/interpreter.ts index bf302df..9553ffb 100644 --- a/src/interpreter.ts +++ b/src/interpreter.ts @@ -172,7 +172,6 @@ export class Interpreter { const entryData = stack.elementsData[i] if (entry.type === 'TaskDef') { AkoElement.TaskDef.execute({vm: this, stack}, entry, entryData, 0.1) - // console.log('Found TaskDef', entry, entryData) } } } diff --git a/src/semantic.ts b/src/semantic.ts index d425d6d..a8b8860 100644 --- a/src/semantic.ts +++ b/src/semantic.ts @@ -96,10 +96,6 @@ export function getGrammar(akoGrammar: string) { While: (a, b, c) => debugWrapper(a, c, AkoElement.LoopWhile.create(b.toAST(), c.toAST())), Foreach: (a, b, _, d, __, f, g) => debugWrapper(a, g, AkoElement.LoopFor.create(b.toAST(), d.toAST(), f.toAST(), g.toAST())), Block: (a, b, c) => debugWrapper(a, c, AkoElement.Block.create(b.toAST())), - // Lambda: (a, b, c, d, e) => { - // console.log(e.sourceString) - // return AkoElement.Lambda.create(b.toAST(), e.toAST()) - // }, LambdaInline: (a, b, _, __, e) => debugWrapper(a, e, AkoElement.Lambda.create(b.toAST(), e.toAST())), Continue: (a) => debugWrapper(a, a, AkoElement.Continue.create()), Return: (a, b) => debugWrapper(a, b, AkoElement.Return.create(b.toAST())),