├── _config.yml ├── .gitignore ├── docs ├── 5-Comprehensive-example.md ├── commemorative │ └── README.md ├── README.md ├── Non-Technical-FAQ.md ├── 6-Deeper-into-SmartC.md ├── 4-Functions-repository.md └── 3-Learning-with-examples.md ├── .eslintrc.js ├── src ├── shaper │ └── shaperTypes.ts ├── __tests__ │ ├── arithmetics.e.spec.ts │ ├── smartc.spec.ts │ ├── warnings.a.spec.ts │ ├── pointers.a.spec.ts │ └── castings.a.spec.ts ├── codeGenerator │ ├── astProcessor │ │ ├── genCode.ts │ │ ├── exceptionAsnProcessor.ts │ │ ├── switchAsnProcessor.ts │ │ ├── endAsnProcessor.ts │ │ └── functionSolver.ts │ ├── assemblyProcessor │ │ ├── keywordToAsm.ts │ │ ├── comparisionToAsm.ts │ │ ├── typeCastingToAsm.ts │ │ ├── optimizerVM │ │ │ └── index.ts │ │ └── operatorToAsm.ts │ ├── codeGeneratorTypes.ts │ └── __tests__ │ │ └── utils.spec.ts ├── syntaxProcessor │ └── syntaxProcessor.ts ├── parser │ └── parser.ts ├── smartc.ts ├── typings │ ├── contractTypes.ts │ └── syntaxTypes.ts ├── assembler │ └── hashMachineCode.ts ├── context.ts └── repository │ └── __tests__ │ └── repository.spec.ts ├── jest.config.js ├── esbuild.config.js ├── sonar-project.properties ├── samples ├── dropper.smartc.c ├── DeadSmartContract.md ├── EchoAnySize.md ├── GetATCreatorID.md ├── ConfigurableTimer.md ├── XmasContest.smartc.c ├── get3random.smartc.c └── PublicMethodsOnSmartC.md ├── .github └── workflows │ └── build.yaml ├── package.json ├── README.md ├── LICENSE ├── CHANGELOG.md ├── debug.html └── tsconfig.json /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | dist 3 | coverage 4 | .vscode -------------------------------------------------------------------------------- /docs/5-Comprehensive-example.md: -------------------------------------------------------------------------------- 1 | [Back](./README.md) 2 | 3 | ## More examples 4 | * Check [samples](https://github.com/deleterium/SmartC/tree/main/samples) on github. 5 | * Check [commemorative](./commemorative/README.md) contracts. 6 | 7 | [Back](./README.md) 8 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2020: true, 5 | jest: true 6 | }, 7 | extends: [ 8 | 'standard' 9 | ], 10 | parser: '@typescript-eslint/parser', 11 | parserOptions: { 12 | ecmaVersion: 2020 13 | }, 14 | plugins: [ 15 | '@typescript-eslint' 16 | ], 17 | rules: { 18 | "indent": ["error", 4], 19 | "camelcase": "warn", 20 | "no-unused-vars": "warn", 21 | "brace-style": [ "warn", "1tbs", {"allowSingleLine": false } ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/shaper/shaperTypes.ts: -------------------------------------------------------------------------------- 1 | export type SHAPER_AUXVARS = { 2 | /** current loop name to be used if break or continue keywords are found. */ 3 | latestLoopId: string[] 4 | /** If true, compilation loop on generator() will not expect the variable 5 | * to be declared. Used in function arguments. */ 6 | isFunctionArgument: boolean 7 | /** Variables scope (function name) */ 8 | currentScopeName:string 9 | /** Prefix to be used in variables names (function name + '_') */ 10 | currentPrefix: string 11 | } 12 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | globals: { 6 | 'ts-jest': { 7 | diagnostics: false 8 | } 9 | }, 10 | collectCoverageFrom: [ 11 | 'src/**/*.ts', 12 | '!**/e2e/**' 13 | ], 14 | transform: { 15 | '^.+\\.ts?$': 'ts-jest' 16 | }, 17 | testPathIgnorePatterns: [ 18 | 'helpers', 19 | '.*\\.e2e\\.ts$', 20 | 'dist', 21 | 'node_modules' 22 | ], 23 | verbose: true 24 | } 25 | -------------------------------------------------------------------------------- /esbuild.config.js: -------------------------------------------------------------------------------- 1 | const esbuild = require('esbuild') 2 | 3 | const configDev = { 4 | entryPoints: ['./src/smartc.js'], 5 | outfile: 'dist/smartc.dev.js', 6 | bundle: true, 7 | minify: false, 8 | platform: 'browser', 9 | sourcemap: 'inline', 10 | target: 'es2020' 11 | } 12 | const configMin = { 13 | entryPoints: ['./src/smartc.js'], 14 | outfile: 'dist/smartc.min.js', 15 | bundle: true, 16 | minify: true, 17 | platform: 'browser', 18 | sourcemap: false, 19 | target: 'es2020' 20 | } 21 | 22 | esbuild.build(configDev).catch(() => process.exit(1)) 23 | esbuild.build(configMin).catch(() => process.exit(1)) 24 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | # Path to sources 2 | sonar.projectKey=deleterium_SmartC 3 | sonar.organization=deleterium 4 | sonar.sources=. 5 | sonar.javascript.lcov.reportPaths=coverage/lcov.info 6 | sonar.test.inclusions=**/*.spec.ts 7 | sonar.cpd.exclusions=**/*template* 8 | sonar.coverage.exclusions=**/*config.js 9 | sonar.c.file.suffixes=- 10 | # This is the name and version displayed in the SonarCloud UI. 11 | #sonar.projectName=SmartC 12 | #sonar.projectVersion=1.0 13 | 14 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. 15 | #sonar.sources=. 16 | 17 | # Encoding of the source code. Default is default system encoding 18 | #sonar.sourceEncoding=UTF-8 19 | -------------------------------------------------------------------------------- /samples/dropper.smartc.c: -------------------------------------------------------------------------------- 1 | #program name Dropper 2 | #program description Sends a fixed amount of signa to a defined account every N blocks. 3 | #program activationAmount 0.2 4 | 5 | // Configure how many blocks to sleep until next send. 6 | #define SLP_BLOCKS 2 7 | // Configure balance to stay on contract 8 | #define CONTRACT_MIN_BALANCE 1.0 9 | // Configure the amount to send every time 10 | #define EACH_BLOCK .2 11 | // Set the desired account 12 | #define RECIPIENT "S-3A2N-ZD7P-KZKU-FPWKQ" 13 | 14 | // Endless loop 15 | while (true) { 16 | if (getCurrentBalanceFx() < CONTRACT_MIN_BALANCE) { 17 | // Stops contracts before it hits end of balance 18 | halt; 19 | } else { 20 | sendAmountFx(EACH_BLOCK, RECIPIENT); 21 | sleep SLP_BLOCKS; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/DeadSmartContract.md: -------------------------------------------------------------------------------- 1 | # Dead Smart Contract 2 | Just a dead smart contract. ~~Any balance sent to this address will be included as fee on the next block. Useful to tip a random miner.~~ Signum Rainbow Hard Fork (june 2022) introduced a change where the contract fees are burned, so this contract now only can be used for burning Signa. Online at `S-DEAD-DTMA-RY8B-FWL3D` 3 | 4 | ## Source code 5 | Very simple source, can be compiled and deployed by SmartC if changed C to Assembly source code. 6 | 7 | ```asm 8 | ^program name DeadSmartContract 9 | ^program description This is a dead smart contract. Any balance sent to this address will be included as fee on the next block. 10 | ^program activationAmount 0 11 | 12 | ^comment This instruction causes code stack underflow on first execution 13 | RET 14 | ``` 15 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: SmartC Tests 2 | 3 | on: [ push ] 4 | 5 | jobs: 6 | build-smartc: 7 | name: Test and Build SmartC 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | with: 13 | fetch-depth: 0 14 | 15 | - uses: actions/setup-node@v4 16 | with: 17 | node-version: 20 18 | 19 | - name: Run SmartC Tests 20 | working-directory: ./ 21 | run: | 22 | npm ci 23 | npm run test:ci 24 | 25 | - name: fix code coverage paths 26 | working-directory: ./coverage 27 | run: sed -i 's@'$GITHUB_WORKSPACE'@/github/workspace/@g' lcov.info 28 | 29 | - name: SonarCloud Scan 30 | uses: SonarSource/sonarcloud-github-action@master 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any 33 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 34 | -------------------------------------------------------------------------------- /docs/commemorative/README.md: -------------------------------------------------------------------------------- 1 | ## v0.1 - SmartC NFT 2 | Be owner of SmartC keywords, support the project and also make an investment! The first multi function and multi items smart contract on Signum blockchain. [Details](./v0.1_SmartC_NFT.md) 3 | 4 | ## v0.2 - Promotional Raffle 227 5 | Advertise your brand making a fair raffle on signum blockchain! [Details](./v0.2_PromotionalRaffle227.md) 6 | 7 | ## v0.3 - Hive, the tumbler 8 | Avoid eavesdroppers to track your transactions and add a layer of obfuscation in your payments. This swarm of 257 contracts is working for you! [Details](./v0.3_Hive_The_Tumbler.md) 9 | 10 | ## v1.0 - SmartC NFT with muscles 11 | Upgraded version of SmartC NFT, featuring dividends distribuiton for owners. Dividends will be provided by NFT sell/transfer fee and from future projects. This smart contract replaces the old one and owners have to migrate. [Details](./v1.0_SmartC_NFT.md) 12 | 13 | ## v2.0 - The Mining Game 14 | Every hour one TMG token is mined by smart contracts deployed by players. 15 | Holders of TMG can even receive Signa from the game. 16 | A kind of "proof-of-donation", where more you donate to players, more chance to mine a block. 17 | [Details](./v2.0_The_Mining_Game.md) 18 | -------------------------------------------------------------------------------- /src/__tests__/arithmetics.e.spec.ts: -------------------------------------------------------------------------------- 1 | import { SmartC } from '../smartc' 2 | 3 | describe('Adress Of', () => { 4 | it('should compile: address of', () => { 5 | const code = 'long a, b; a=(b);' 6 | const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare a\n^declare b\n\nSET @a $b\nFIN\n' 7 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 8 | compiler.compile() 9 | expect(compiler.getAssemblyCode()).toBe(assembly) 10 | }) 11 | it('should compile: address of register', () => { 12 | const code = 'long a, *b; b = &r1;' 13 | const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare a\n^declare b\n\nSET @b #0000000000000001\nFIN\n' 14 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 15 | compiler.compile() 16 | expect(compiler.getAssemblyCode()).toBe(assembly) 17 | }) 18 | test('should throw: deferencing variable not pointer on left side', () => { 19 | expect(() => { 20 | const code = 'long a, b, c, d; *(a+1)=b;' 21 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 22 | compiler.compile() 23 | }).toThrowError(/^At line/) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /src/codeGenerator/astProcessor/genCode.ts: -------------------------------------------------------------------------------- 1 | import { CONTRACT } from '../../typings/contractTypes' 2 | import { GENCODE_ARGS, GENCODE_SOLVED_OBJECT } from '../codeGeneratorTypes' 3 | 4 | import utils from '../utils' 5 | import binaryAsnProcessor from './binaryAsnProcessor' 6 | import endAsnProcessor from './endAsnProcessor' 7 | import exceptionAsnProcessor from './exceptionAsnProcessor' 8 | import lookupAsnProcessor from './lookupAsnProcessor' 9 | import switchAsnProcessor from './switchAsnProcessor' 10 | import unaryAsnProcessor from './unaryAsnProcessor' 11 | 12 | /** Manages the functions to process Abstract Syntax Nodes */ 13 | export default function genCode ( 14 | Program: CONTRACT, ScopeInfo: GENCODE_ARGS 15 | ) : GENCODE_SOLVED_OBJECT { 16 | switch (ScopeInfo.RemAST.type) { 17 | case 'nullASN': 18 | return { SolvedMem: utils.createVoidMemObj(), asmCode: '' } 19 | case 'endASN': 20 | return endAsnProcessor(Program, ScopeInfo) 21 | case 'lookupASN': 22 | return lookupAsnProcessor(Program, ScopeInfo) 23 | case 'unaryASN': 24 | return unaryAsnProcessor(Program, ScopeInfo) 25 | case 'exceptionASN': 26 | return exceptionAsnProcessor(Program, ScopeInfo) 27 | case 'binaryASN': 28 | return binaryAsnProcessor(Program, ScopeInfo) 29 | case 'switchASN': 30 | return switchAsnProcessor(Program, ScopeInfo) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # SmartC 2 | Write C smart contracts for signum network. Compile in your browser. Written in Typescript/Javascript. 3 | 4 | ## Objective 5 | To empower developers, allowing them to create complex and highly optimized smart contracts. 6 | 7 | ## Documentation 8 | 9 | ### General information: 10 | * [Non-Technical FAQ](./Non-Technical-FAQ.md) 11 | 12 | ### Videos 13 | * [SmartC playlist on Youtube](https://www.youtube.com/playlist?list=PLyu0NNtb1eg3Gcg2JCrOle8MjtuFPb-Gi), with videos for starting, simulating and deploying a smart contract. 14 | 15 | ### Technical documentation: 16 | * [Basis](./1-Basis.md) 17 | * [Preprocessor directives](./1.2-Preprocessor-directives.md) 18 | * [Built-in functions](./1.5-Built-in-functions.md) 19 | * [API Functions](./2-API-Pseudo-Code.md) 20 | * [Learning with examples](./3-Learning-with-examples.md) 21 | * [Functions repository](./4-Functions-repository.md) 22 | * [Comprehensive example](./5-Comprehensive-example.md) 23 | * [Deeper into SmartC](./6-Deeper-into-SmartC.md) 24 | 25 | ### Commemorative Smart Contracts 26 | * Check [Commemorative](./commemorative/README.md) smart contracts source code for complex cases. 27 | 28 | ## Social media 29 | Join [SmartC Compiler](https://discord.gg/pQHnBRYE5c) server in Discord to stay tuned for news or ask questions. 30 | 31 | ## Support 32 | Did you like the project? Consider be owner of one SmartC NFT keyword. The smart contract is online at S-NFT2-6MA4-KLA2-DNM8T. More information on [NFTv2 website](https://deleterium.info/NFTv2/). My address on signum: S-DKVF-VE8K-KUXB-DELET. 33 | 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smartc-signum-compiler", 3 | "version": "9999.9.6", 4 | "description": "C Compiler for smart contracts on Signum network", 5 | "main": "dist/smartc.js", 6 | "types": "dist/smartc.d.ts", 7 | "files": [ 8 | "dist" 9 | ], 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/deleterium/SmartC.git" 13 | }, 14 | "keywords": [ 15 | "smartc", 16 | "compiler", 17 | "blockchain", 18 | "c", 19 | "signum" 20 | ], 21 | "author": "Rui Deleterium", 22 | "private": false, 23 | "license": "BSD-3-Clause", 24 | "bugs": { 25 | "url": "https://github.com/deleterium/SmartC/issues" 26 | }, 27 | "homepage": "https://github.com/deleterium/SmartC#readme", 28 | "scripts": { 29 | "lint": "npx eslint './src/**/*'", 30 | "test": "JEST=true npx jest", 31 | "test:ci": "JEST=true npx jest --coverage", 32 | "debug": "node esbuild.config.js && npx light-server -s . -p 7002 --no-reload --historyindex '/debug.html'", 33 | "build": "npm run lint && node esbuild.config.js && npx tsc" 34 | }, 35 | "devDependencies": { 36 | "@types/jest": "^27.5.1", 37 | "@typescript-eslint/eslint-plugin": "^4.33.0", 38 | "@typescript-eslint/parser": "^4.33.0", 39 | "esbuild": "^0.13.15", 40 | "eslint": "^7.32.0", 41 | "eslint-config-standard": "^16.0.3", 42 | "eslint-plugin-import": "^2.26.0", 43 | "eslint-plugin-node": "^11.1.0", 44 | "eslint-plugin-promise": "^5.2.0", 45 | "globule": "^1.3.4", 46 | "jest": "^27.5.1", 47 | "light-server": "^2.9.1", 48 | "ts-jest": "^27.1.5", 49 | "typescript": "^4.4.4" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/codeGenerator/astProcessor/exceptionAsnProcessor.ts: -------------------------------------------------------------------------------- 1 | import { assertNotUndefined } from '../../repository/repository' 2 | import { CONTRACT } from '../../typings/contractTypes' 3 | import { createInstruction } from '../assemblyProcessor/createInstruction' 4 | import { GENCODE_ARGS, GENCODE_SOLVED_OBJECT } from '../codeGeneratorTypes' 5 | import utils from '../utils' 6 | import genCode from './genCode' 7 | 8 | export default function exceptionAsnProcessor ( 9 | Program: CONTRACT, ScopeInfo: GENCODE_ARGS 10 | ) : GENCODE_SOLVED_OBJECT { 11 | const CurrentNode = utils.assertAsnType('exceptionASN', ScopeInfo.RemAST) 12 | if (ScopeInfo.jumpFalse !== undefined) { 13 | throw new Error(Program.Context.formatError(CurrentNode.Operation.line, 14 | 'Can not use SetUnaryOperator (++ or --) during logical operations with branches')) 15 | } 16 | if (CurrentNode.Left !== undefined) { 17 | const LGenObj = genCode(Program, { 18 | RemAST: CurrentNode.Left, 19 | logicalOp: false, 20 | revLogic: ScopeInfo.revLogic, 21 | jumpFalse: ScopeInfo.jumpFalse, 22 | jumpTrue: ScopeInfo.jumpTrue 23 | }) 24 | LGenObj.asmCode += createInstruction(Program, CurrentNode.Operation, LGenObj.SolvedMem) 25 | return LGenObj 26 | } 27 | const RGenObj = genCode(Program, { 28 | RemAST: assertNotUndefined(CurrentNode.Right), 29 | logicalOp: false, 30 | revLogic: ScopeInfo.revLogic, 31 | jumpFalse: ScopeInfo.jumpFalse, 32 | jumpTrue: ScopeInfo.jumpTrue 33 | }) 34 | Program.Context.SentenceContext.postOperations += createInstruction(Program, CurrentNode.Operation, RGenObj.SolvedMem) 35 | return RGenObj 36 | } 37 | -------------------------------------------------------------------------------- /src/codeGenerator/assemblyProcessor/keywordToAsm.ts: -------------------------------------------------------------------------------- 1 | import { assertNotUndefined } from '../../repository/repository' 2 | import { CONTRACT } from '../../typings/contractTypes' 3 | import { TOKEN, MEMORY_SLOT } from '../../typings/syntaxTypes' 4 | import { FLATTEN_MEMORY_RETURN_OBJECT } from '../codeGeneratorTypes' 5 | import { flattenMemory } from './createInstruction' 6 | 7 | /** 8 | * Create instruction for keywords asm, break, continue, exit, goto, halt, label, return and sleep. 9 | */ 10 | export default function keywordToAsm ( 11 | Program: CONTRACT, OperatorToken: TOKEN, FlatMem?: MEMORY_SLOT 12 | ): string { 13 | let TmpMemObj: FLATTEN_MEMORY_RETURN_OBJECT 14 | switch (OperatorToken.value) { 15 | case 'break': 16 | return `JMP :${Program.Context.getLatestLoopID()}_${OperatorToken.value}\n` 17 | case 'continue': 18 | return `JMP :${Program.Context.getLatestPureLoopID()}_${OperatorToken.value}\n` 19 | case 'goto': 20 | return `JMP :${assertNotUndefined(FlatMem).name}\n` 21 | case 'halt': 22 | return 'STP\n' 23 | case 'exit': 24 | return 'FIN\n' 25 | case 'return': 26 | if (FlatMem === undefined) { 27 | return 'RET\n' 28 | } 29 | TmpMemObj = flattenMemory(Program, FlatMem, OperatorToken.line) 30 | TmpMemObj.asmCode += `SET @r0 $${TmpMemObj.FlatMem.asmName}\n` 31 | TmpMemObj.asmCode += 'RET\n' 32 | Program.Context.freeRegister(FlatMem.address) 33 | if (TmpMemObj.isNew === true) { 34 | Program.Context.freeRegister(TmpMemObj.FlatMem.address) 35 | } 36 | return TmpMemObj.asmCode 37 | case 'sleep': 38 | if (FlatMem === undefined) { 39 | return 'SLP\n' 40 | } 41 | TmpMemObj = flattenMemory(Program, assertNotUndefined(FlatMem), OperatorToken.line) 42 | TmpMemObj.asmCode += `SLP $${TmpMemObj.FlatMem.asmName}\n` 43 | Program.Context.freeRegister(FlatMem.address) 44 | if (TmpMemObj.isNew === true) { 45 | Program.Context.freeRegister(TmpMemObj.FlatMem.address) 46 | } 47 | return TmpMemObj.asmCode 48 | case 'asm': { 49 | let lines = assertNotUndefined(OperatorToken.extValue).split('\n') 50 | lines = lines.map(value => value.trim()) 51 | return lines.join('\n').trim() + '\n' 52 | } 53 | default: 54 | throw new Error('Internal error.') 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/__tests__/smartc.spec.ts: -------------------------------------------------------------------------------- 1 | import { SmartC } from '../smartc' 2 | 3 | describe('SmartC class (no compiling)', () => { 4 | test('wrong language', () => { 5 | expect(() => { 6 | // eslint-disable-next-line 7 | const compiler = new SmartC({ 8 | // @ts-expect-error 9 | language: 'c', 10 | sourceCode: '' 11 | }) 12 | compiler.compile() 13 | }).toThrow() 14 | }) 15 | test('wrong usage', () => { 16 | expect(() => { 17 | const compiler = new SmartC({ 18 | language: 'C', 19 | sourceCode: '' 20 | }) 21 | compiler.getAssemblyCode() 22 | }).toThrow() 23 | }) 24 | test('wrong usage', () => { 25 | expect(() => { 26 | const compiler = new SmartC({ 27 | language: 'C', 28 | sourceCode: '' 29 | }) 30 | compiler.getMachineCode() 31 | }).toThrow() 32 | }) 33 | }) 34 | describe('SmartC class (void compiling)', () => { 35 | it('should compile sucessfull void C code 1x', () => { 36 | const compiler = new SmartC({ language: 'C', sourceCode: '' }) 37 | expect(compiler.compile().getAssemblyCode()).toBe('^declare r0\n^declare r1\n^declare r2\n\nFIN\n') 38 | }) 39 | it('should compile sucessfull void C code 2x', () => { 40 | const compiler = new SmartC({ language: 'C', sourceCode: '' }) 41 | compiler.compile() 42 | compiler.compile() 43 | expect(compiler.getAssemblyCode()).toBe('^declare r0\n^declare r1\n^declare r2\n\nFIN\n') 44 | }) 45 | it('should compile sucessfull void Assembly code', () => { 46 | const compiler = new SmartC({ language: 'Assembly', sourceCode: '' }) 47 | expect(compiler.compile().getAssemblyCode()).toBe('') 48 | expect(compiler.getMachineCode().ByteCode).toBe('') 49 | }) 50 | it('should compile sucessfull void C code 1x (Same compiler version) ', () => { 51 | const testVersion = new SmartC({ language: 'C', sourceCode: '' }) 52 | const version = testVersion.getCompilerVersion() 53 | const compiler = new SmartC({ language: 'C', sourceCode: `#pragma version ${version}\n` }) 54 | expect(typeof version).toBe('string') 55 | expect(version).not.toBe('') 56 | expect(compiler.compile().getAssemblyCode()).toBe('^declare r0\n^declare r1\n^declare r2\n\nFIN\n') 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /samples/EchoAnySize.md: -------------------------------------------------------------------------------- 1 | # Echo Any Size 2 | Simple contract that reads the incoming text message until a zero byte is found on last byte of last page read. Clears the rest of message buffer and then send the "same message" to sender. Smart contracts only can send 32 bytes each time, so expect the received message to be multiple of 32, padded with zero bytes. Online at tesnet `TS-LZYH-PE75-JZTB-FJ88Y`. Note that there is no API to get the message size, so the program must handle the input end in some way. Activation amount is huge because the fees to read/send info for a smart contract are much higher than sending manually and the contract must handle input text up the 1000 bytes, the current blockchain limit. 3 | 4 | ## Source code 5 | 6 | ```c 7 | #program name EchoAnySize 8 | #program description Reads the incoming message until a zero byte\ 9 | is found on last byte of last page read. Clears the rest of buffer\ 10 | and then send the same message to sender. Expect text messages. 11 | #program activationAmount 5_0000_0000 12 | 13 | struct TXINFO { 14 | long txId; 15 | long sender; 16 | long amount; 17 | long message[132]; 18 | } currentTX; 19 | 20 | long zero; 21 | 22 | while (true) 23 | { 24 | while ((currentTX.txId = getNextTx()) != 0) { 25 | currentTX.sender = getSender(currentTX.txId); 26 | currentTX.amount = getAmount(currentTX.txId); 27 | readMessage(currentTX.txId, 0, currentTX.message); 28 | 29 | processTX(); 30 | } 31 | sendBalance(getCreator()); 32 | } 33 | 34 | // just echoes a received message back to sender. 35 | void processTX(void) { 36 | 37 | long messagePage, currentLong; 38 | 39 | // Last read on getNextTxDetails 40 | currentLong = 4; 41 | while (currentLong < currentTX.message.length) { 42 | if (((currentTX.message[currentLong - 1]) >> 56) == 0) { 43 | // Found a null byte at last byte of last page that was read. 44 | break; 45 | } 46 | messagePage = currentLong / 4; 47 | readMessage(currentTX.txId, messagePage, currentTX.message + currentLong); 48 | currentLong += 4; 49 | } 50 | while (currentLong < currentTX.message.length) { 51 | // clear the rest of buffer. 52 | currentTX.message[currentLong++] = zero; 53 | currentTX.message[currentLong++] = zero; 54 | currentTX.message[currentLong++] = zero; 55 | currentTX.message[currentLong++] = zero; 56 | } 57 | currentLong = 0; 58 | do { 59 | // send message loop 60 | sendMessage(currentTX.message + currentLong, currentTX.sender); 61 | currentLong += 4; 62 | } while (((currentTX.message[currentLong - 1]) >> 56) != 0 && currentLong < currentTX.message.length); 63 | } 64 | ``` 65 | -------------------------------------------------------------------------------- /samples/GetATCreatorID.md: -------------------------------------------------------------------------------- 1 | # Get AT Creator ID 2 | Simple contract that receives a message with some AT ID (text message in decimal representation) and return to sender a message with the creator's ID of that AT (also in text unsigned decimal representation). Online at tesnet `TS-7MUA-SSZ8-W6QR-6M892` 3 | 4 | ## Source code 5 | Note that this contract is currently not working on stable. It is for reference only and to be updated when the hard fork is online. 6 | ```c 7 | #program name GetATCreator 8 | #program description Receives a message with some AT ID and return to sender a\ 9 | message with the creator`s ID of that AT. 10 | #program activationAmount 1_5000_0000 11 | 12 | struct TXINFO { 13 | long txId; 14 | long sender; 15 | long amount; 16 | long message[4]; 17 | } currentTX; 18 | 19 | long messageToSend[4]; 20 | 21 | while (true) 22 | { 23 | while ((currentTX.txId = getNextTx()) != 0) { 24 | currentTX.sender = getSender(currentTX.txId); 25 | currentTX.amount = getAmount(currentTX.txId); 26 | readMessage(currentTX.txId, 0, currentTX.message); 27 | 28 | processTX(); 29 | } 30 | sendBalance(getCreator()); 31 | } 32 | 33 | // Return to sender the creator of a given AT. 34 | void processTX(void) { 35 | long atId = messageToId(); 36 | long creatorID = getCreatorOf(atId); 37 | IdToMessage(creatorID); 38 | sendMessage(messageToSend, currentTX.sender); 39 | } 40 | 41 | 42 | long i, auxDiv, auxShift, auxMask, auxNum; 43 | const long n8 = 8, n10 = 10, n15 = 15, n48 = 48, n57 = 57, n255 = 255; 44 | void IdToMessage(long id){ 45 | long currDiv = 10; 46 | messageToSend[] = "00000000000000000000 "; 47 | // using i as temp var; 48 | i = (id >> 1) / 5; 49 | messageToSend[2] |= (id - (i * 10)) << 24; 50 | id = i; 51 | 52 | for (i = 18; id != 0; i--) { 53 | auxNum = id % currDiv; 54 | id /= 10; 55 | auxDiv = i/8; 56 | auxShift = (i % 8) * 8; 57 | auxMask = 0xff << auxShift; 58 | messageToSend[i/8] |= auxNum << auxShift; 59 | } 60 | } 61 | 62 | // Expects a numeric ID in currentTX.message[0] to [3] 63 | // return its long representation 64 | long messageToId(void) { 65 | long currMul = 1; 66 | long ret=0; 67 | 68 | for (i = 19; i>=0; i--) { 69 | auxDiv = i/8; 70 | auxShift = (i % 8) * 8; 71 | auxMask = 0xff << auxShift; 72 | auxNum = (currentTX.message[i/8] & auxMask) >> auxShift; 73 | if (auxNum == 0) { 74 | continue; 75 | } 76 | if (auxNum < '0' || auxNum > '9' ) { 77 | // invalid char 78 | return 0; 79 | } 80 | auxNum &= 0xF; 81 | auxNum *= currMul; 82 | ret += auxNum; 83 | currMul *= 10; 84 | } 85 | return ret; 86 | } 87 | ``` 88 | -------------------------------------------------------------------------------- /docs/Non-Technical-FAQ.md: -------------------------------------------------------------------------------- 1 | [Back](./README.md) 2 | 3 | # Non-Technical frequently asked questions 4 | 5 | ### SmartC is written in what programming language? 6 | It is written in Typescript to take advantage of strong typing for objects, then it is transpiled to Javascript to run entirely on browser. 7 | 8 | ### Why did you write the compiler in Typescript? 9 | It is very convenient for users, so compilation process can be done on a regular web page, no need to install the software. Also Typescript has many features to work with objects, the basis for compilation process. 10 | 11 | ### Why to create a new compiler for signum if there is already one? 12 | I would like to know more about compilers and compilation process. I started slowly with one simple Assembly project (BurstAT/SimpleIDE). Then, more I learned, easier was to make a better compiler. The project is my hobby, so I can work on it without pressure, doing the best I can. 13 | 14 | ### What is the language for smart contracts source code? 15 | The language is similar to C but there are diferences. Actually a program for SmartC will only compile in SmartC, so it is possible to call this language as SmartC. When saving my programs, I use to save as **name.smartc.c** so I can get C syntax highlight in others editors or IDE's. 16 | 17 | ### Why did you choose C language to smart contracts? 18 | It is simple and not so hard to create a compiler. Also it was my first programming language and I like it! The language is also powerfull and close to assembly language, so the compiled smart contract is very optimized and this feature is indispensable for complex smart contracts. 19 | 20 | ### Can SmartC compile code from SmartJ? 21 | No, they are different compilers for contracts source code in different programming languages. 22 | 23 | ### How much cost to deploy a contract? 24 | It depends on contract size. When you compile code smartC will show the and set deploy fee to the minimun necessary. The biggest contract possible is around 2.5 signa. Most of them are less than 1 signa and small ones are around .5 signa. This fee is charged only one time at deployment. 25 | 26 | ### How much cost to run a contract? 27 | It is necessary to pay a little fee for every instruction processed. The value is low but big contracts will build up this charge. So it can vary from 0.000735 to more than one hundred signa in one block! 28 | 29 | ### How to avoid the contract to run out of balance during one run? 30 | Just set a higher minimum activation amount, to ensure the contract will have balance (or gas) to run until the end. If a transaction arrives with some amount below this minimum amount, it will not be processed by the contract, but the balance sent will increase contract balance. 31 | 32 | ### What happens if I send a value higher than activation amount to the contract? 33 | The contract will be activated in next block and do what it is programed to do. The value above the activation amount will be the value that the contract reads as transaction amount received. 34 | 35 | [Back](./README.md) 36 | -------------------------------------------------------------------------------- /src/codeGenerator/assemblyProcessor/comparisionToAsm.ts: -------------------------------------------------------------------------------- 1 | import { CONTRACT } from '../../typings/contractTypes' 2 | import { TOKEN, MEMORY_SLOT } from '../../typings/syntaxTypes' 3 | import { flattenMemory } from './createInstruction' 4 | 5 | /** Create assembly intructions for comparisions. 6 | * @returns the assembly code necessary for branch operations 7 | */ 8 | export default function comparisionToAsm ( 9 | Program: CONTRACT, OperatorToken: TOKEN, LeftMem: MEMORY_SLOT, RightMem: MEMORY_SLOT, 10 | rLogic:boolean, jpFalse: string, jpTrue:string 11 | ): string { 12 | let assemblyCode = '' 13 | let jumpToLabel = jpFalse 14 | if (rLogic) { 15 | jumpToLabel = jpTrue 16 | } 17 | const FlatLeft = flattenMemory(Program, LeftMem, OperatorToken.line) 18 | if (FlatLeft.isNew) { 19 | if (LeftMem.Offset?.type === 'variable') { 20 | Program.Context.freeRegister(LeftMem.Offset.addr) 21 | } 22 | Program.Context.freeRegister(LeftMem.address) 23 | } 24 | if (RightMem.type === 'constant' && RightMem.hexContent === '0000000000000000' && 25 | (OperatorToken.value === '==' || OperatorToken.value === '!=')) { 26 | assemblyCode += chooseBranchZero(OperatorToken.value, rLogic) 27 | assemblyCode += ` $${FlatLeft.FlatMem.asmName} :${jumpToLabel}\n` 28 | if (FlatLeft.isNew === true) { 29 | Program.Context.freeRegister(FlatLeft.FlatMem.address) 30 | } 31 | return FlatLeft.asmCode + assemblyCode 32 | } 33 | const FlatRight = flattenMemory(Program, RightMem, OperatorToken.line) 34 | assemblyCode += chooseBranch(OperatorToken.value, rLogic) 35 | assemblyCode += ` $${FlatLeft.FlatMem.asmName} $${FlatRight.FlatMem.asmName} :${jumpToLabel}\n` 36 | if (FlatLeft.isNew === true) { 37 | Program.Context.freeRegister(FlatLeft.FlatMem.address) 38 | } 39 | if (FlatRight.isNew === true) { 40 | Program.Context.freeRegister(FlatRight.FlatMem.address) 41 | } 42 | return FlatLeft.asmCode + FlatRight.asmCode + assemblyCode 43 | } 44 | 45 | function chooseBranch (value: string, cbRevLogic: boolean): string { 46 | let operator = '' 47 | switch (value) { 48 | case '>': operator = 'BLE'; break 49 | case '<': operator = 'BGE'; break 50 | case '>=': operator = 'BLT'; break 51 | case '<=': operator = 'BGT'; break 52 | case '==': operator = 'BNE'; break 53 | case '!=': operator = 'BEQ'; break 54 | } 55 | if (cbRevLogic === false) { 56 | return operator 57 | } 58 | switch (value) { 59 | case '>': return 'BGT' 60 | case '<': return 'BLT' 61 | case '>=': return 'BGE' 62 | case '<=': return 'BLE' 63 | case '==': return 'BEQ' 64 | case '!=': return 'BNE' 65 | } 66 | return 'Internal error.' 67 | } 68 | 69 | function chooseBranchZero (value: '=='|'!=', cbRevLogic: boolean) : string { 70 | if (cbRevLogic) { 71 | if (value === '==') return 'BZR' 72 | return 'BNZ' 73 | } 74 | if (value === '==') return 'BNZ' 75 | return 'BZR' 76 | } 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SmartC 2 | > Write C smart contracts for signum network. Compile in your browser. Written in Typescript/Javascript. 3 | 4 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=deleterium_SmartC&metric=alert_status)](https://sonarcloud.io/dashboard?id=deleterium_SmartC) 5 | [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=deleterium_SmartC&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=deleterium_SmartC) 6 | [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=deleterium_SmartC&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=deleterium_SmartC) 7 | [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=deleterium_SmartC&metric=coverage)](https://sonarcloud.io/dashboard?id=deleterium_SmartC) 8 | 9 | ## Objective 10 | To empower developers, allowing them to create complex and highly optimized smart contracts. 11 | 12 | # Setup 13 | This library can be obtained through npm: 14 | ``` 15 | npm install smartc-signum-compiler 16 | ``` 17 | 18 | The stable version is released under tag `@latest` and the development under `@next`. 19 | 20 | # Usage 21 | 22 | ## Web User Interface 23 | A web user interface project is available at https://github.com/deleterium/smartc-web-ui If you want just to code with SmartC use https://deleterium.info/SmartC/ 24 | 25 | ## Documentation / FAQ / Lessons 26 | Docs files can be found in this repo, at `doc` folder. 27 | 28 | ## Node 29 | ```ts 30 | import { SmartC } from 'smartc-signum-compiler'; 31 | 32 | // Example: Simple compilation test 33 | try { 34 | const startUpTest = new SmartC({ 35 | language: 'C', 36 | sourceCode: '#pragma maxAuxVars 1\nlong a, b, c; a=b/~c;' 37 | }) 38 | startUpTest.compile() 39 | const assemblyText = startUpTest.getAssemblyCode() 40 | const machineObject = startUpTest.getMachineCode() 41 | // Do something 42 | } catch (e) { 43 | return "Compilation error: " + e.message 44 | } 45 | ``` 46 | 47 | ## Browser 48 | Import the minified javascript file. SmartC will be imported as global. 49 | ```html 50 | 51 | ``` 52 | 53 | Then in your javascript file, just use it: 54 | ```js 55 | // Example: Simple compilation test 56 | try { 57 | const startUpTest = new SmartC({ 58 | language: 'C', 59 | sourceCode: '#pragma maxAuxVars 1\nlong a, b, c; a=b/~c;' 60 | }) 61 | startUpTest.compile() 62 | const assemblyText = startUpTest.getAssemblyCode() 63 | const machineObject = startUpTest.getMachineCode() 64 | // Do something 65 | } catch (e) { 66 | return "Compilation error: " + e.message 67 | } 68 | ``` 69 | 70 | ## Changelog 71 | Find [here](https://deleterium.github.io/SmartC/CHANGELOG) major upgrades between releases. 72 | 73 | ## Support 74 | Did you like the project? Consider be owner of one SmartC NFT keyword. The smart contract is online at S-NFT2-6MA4-KLA2-DNM8T. More information on [NFTv2 website](https://deleterium.info/NFTv2/). My address: S-DKVF-VE8K-KUXB-DELET. 75 | 76 | ## Social media 77 | Join [SmartC Compiler](https://discord.gg/pQHnBRYE5c) server in Discord to stay tuned for news or ask questions. 78 | -------------------------------------------------------------------------------- /src/codeGenerator/astProcessor/switchAsnProcessor.ts: -------------------------------------------------------------------------------- 1 | import { CONTRACT } from '../../typings/contractTypes' 2 | import { SWITCH_ASN } from '../../typings/syntaxTypes' 3 | import { createInstruction, createSimpleInstruction } from '../assemblyProcessor/createInstruction' 4 | import { GENCODE_ARGS, GENCODE_SOLVED_OBJECT } from '../codeGeneratorTypes' 5 | import utils from '../utils' 6 | import genCode from './genCode' 7 | 8 | export default function switchAsnProcessor ( 9 | Program: CONTRACT, ScopeInfo: GENCODE_ARGS 10 | ) : GENCODE_SOLVED_OBJECT { 11 | let CurrentNode: SWITCH_ASN 12 | 13 | function switchAsnProcessorMain () : GENCODE_SOLVED_OBJECT { 14 | CurrentNode = utils.assertAsnType('switchASN', ScopeInfo.RemAST) 15 | let assemblyCode = '' 16 | 17 | const Expression = genCode(Program, { 18 | RemAST: CurrentNode.Expression, 19 | logicalOp: false, 20 | revLogic: ScopeInfo.revLogic 21 | }) 22 | assemblyCode += Expression.asmCode 23 | 24 | if (Expression.SolvedMem.type === 'constant') { 25 | if (Expression.SolvedMem.hexContent === '0000000000000000') { 26 | return switchLogical(false) 27 | } 28 | return switchLogical(true) 29 | } 30 | CurrentNode.caseConditions.forEach((Cond, index) => { 31 | const OneCondition = genCode(Program, { 32 | RemAST: Cond, 33 | logicalOp: false, 34 | revLogic: ScopeInfo.revLogic 35 | }) 36 | assemblyCode += OneCondition.asmCode 37 | assemblyCode += createInstruction( 38 | Program, 39 | utils.genNotEqualToken(), 40 | Expression.SolvedMem, 41 | OneCondition.SolvedMem, 42 | ScopeInfo.revLogic, 43 | ScopeInfo.jumpFalse + '_' + index, 44 | ScopeInfo.jumpTrue 45 | ) 46 | }) 47 | assemblyCode += createSimpleInstruction('Jump', ScopeInfo.jumpTrue) 48 | return { 49 | SolvedMem: utils.createVoidMemObj(), 50 | asmCode: assemblyCode 51 | } 52 | } 53 | 54 | function switchLogical (switchTrue: boolean) : GENCODE_SOLVED_OBJECT { 55 | let assemblyCode = '' 56 | CurrentNode.caseConditions.forEach((Cond, index) => { 57 | const Args = { 58 | RemAST: Cond, 59 | logicalOp: true, 60 | revLogic: true, 61 | jumpFalse: ScopeInfo.jumpFalse + '_' + index + '_next', 62 | jumpTrue: ScopeInfo.jumpFalse + '_' + index 63 | } 64 | if (!switchTrue) { 65 | Args.revLogic = false 66 | Args.jumpFalse = ScopeInfo.jumpFalse + '_' + index 67 | Args.jumpTrue = ScopeInfo.jumpFalse + '_' + index + '_next' 68 | } 69 | const OneCondition = genCode(Program, Args) 70 | assemblyCode += OneCondition.asmCode 71 | assemblyCode += createSimpleInstruction('Label', ScopeInfo.jumpFalse + '_' + index + '_next') 72 | }) 73 | assemblyCode += createSimpleInstruction('Jump', ScopeInfo.jumpTrue) 74 | return { 75 | SolvedMem: utils.createVoidMemObj(), 76 | asmCode: assemblyCode 77 | } 78 | } 79 | 80 | return switchAsnProcessorMain() 81 | } 82 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, Rui Santana 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | ***************************************************************************** 32 | 33 | Parts of this project based on https://github.com/Captainarash/CaptCC project, 34 | that also have BDS 3-Clause License. Files tokenizer.js and parser.js have 35 | license as below: 36 | 37 | ***************************************************************************** 38 | 39 | BSD 3-Clause License 40 | 41 | Copyright (c) 2017, Arash Tohidi Chafi 42 | All rights reserved. 43 | 44 | Redistribution and use in source and binary forms, with or without 45 | modification, are permitted provided that the following conditions are met: 46 | 47 | * Redistributions of source code must retain the above copyright notice, this 48 | list of conditions and the following disclaimer. 49 | 50 | * Redistributions in binary form must reproduce the above copyright notice, 51 | this list of conditions and the following disclaimer in the documentation 52 | and/or other materials provided with the distribution. 53 | 54 | * Neither the name of the copyright holder nor the names of its 55 | contributors may be used to endorse or promote products derived from 56 | this software without specific prior written permission. 57 | 58 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 59 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 60 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 61 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 62 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 63 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 64 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 65 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 66 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 67 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 68 | -------------------------------------------------------------------------------- /src/syntaxProcessor/syntaxProcessor.ts: -------------------------------------------------------------------------------- 1 | import { CONTRACT } from '../typings/contractTypes' 2 | import { SENTENCES } from '../typings/syntaxTypes' 3 | import { assertNotUndefined } from '../repository/repository' 4 | import createTree from './createTree' 5 | 6 | /** 7 | * Traverse Program transforming some sentences properties from arrays of 8 | * tokens into an actually abstract syntax tree. Check operators 9 | * precedence and let operations in correct order for assembler. 10 | * This is parser third and final pass. 11 | * @param Program to be processed 12 | * @returns {void} but Program will be updated. 13 | * @throws {Error} on any mistake. 14 | */ 15 | export default function syntaxProcessor (Program: CONTRACT) : void { 16 | /* * * Main function! * * */ 17 | function syntaxProcessMain () : void { 18 | Program.Global.sentences.forEach(processSentence) 19 | Program.functions.forEach(CurrentFunction => { 20 | CurrentFunction.sentences.forEach(processSentence) 21 | }) 22 | } 23 | 24 | // Process recursively one Sentence object, creating an CodeAST, that was 25 | // processed sintacticly. 26 | function processSentence (SentenceObj: SENTENCES) { 27 | switch (SentenceObj.type) { 28 | case 'phrase': 29 | try { 30 | SentenceObj.CodeAST = createTree( 31 | Program, 32 | assertNotUndefined(SentenceObj.code, 'Internal error. Unknow object arrived at processSentence') 33 | ) 34 | delete SentenceObj.code 35 | } catch (err) { 36 | if (err instanceof Error) { 37 | Program.Context.errors.push(err.message) 38 | // delete the offending sentence and continue. 39 | SentenceObj.CodeAST = { 40 | type: 'nullASN' 41 | } 42 | delete SentenceObj.code 43 | break 44 | } 45 | // Fatal error 46 | throw err 47 | } 48 | break 49 | case 'scope': 50 | SentenceObj.alwaysBlock.forEach(processSentence) 51 | break 52 | case 'ifElse': 53 | SentenceObj.falseBlock.forEach(processSentence) 54 | // eslint-disable-next-line no-fallthrough 55 | case 'ifEndif': 56 | case 'while': 57 | case 'do': 58 | if (assertNotUndefined(SentenceObj.condition).length === 0) { 59 | throw new Error(Program.Context.formatError(SentenceObj.line, 'Sentence condition can not be empty.')) 60 | } 61 | SentenceObj.ConditionAST = createTree(Program, SentenceObj.condition) 62 | delete SentenceObj.condition 63 | SentenceObj.trueBlock.forEach(processSentence) 64 | break 65 | case 'for': 66 | SentenceObj.threeSentences.forEach(processSentence) 67 | SentenceObj.trueBlock.forEach(processSentence) 68 | break 69 | case 'struct': 70 | processSentence(SentenceObj.Phrase) 71 | break 72 | case 'switch': 73 | SentenceObj.JumpTable = { 74 | type: 'switchASN', 75 | Expression: createTree(Program, SentenceObj.expression), 76 | caseConditions: assertNotUndefined(SentenceObj.cases).map(Sentence => createTree(Program, Sentence)) 77 | } 78 | delete SentenceObj.expression 79 | delete SentenceObj.cases 80 | SentenceObj.block.forEach(processSentence) 81 | break 82 | case 'case': 83 | delete SentenceObj.condition 84 | break 85 | } 86 | } 87 | 88 | return syntaxProcessMain() 89 | } 90 | -------------------------------------------------------------------------------- /src/__tests__/warnings.a.spec.ts: -------------------------------------------------------------------------------- 1 | import { SmartC } from '../smartc' 2 | 3 | describe('Warnings', () => { 4 | it('should not warn: add or sub pointers with longs values (struct_ptr)', () => { 5 | const code = 'search(2);\nstruct PLAYER { long address, balance, VDLS; } players[2];\nstruct PLAYER * search(long playerAddress) {\nlong nPlayers=0;\n struct PLAYER * foundPlayer;\n foundPlayer = &players[0];\n for (long auxI = 0; auxI < nPlayers; auxI++) {\n if (foundPlayer->address == playerAddress) {\n return foundPlayer;\n }\n foundPlayer += (sizeof(struct PLAYER));\n foundPlayer = foundPlayer + (sizeof(struct PLAYER));\n foundPlayer++;\n }\n return NULL;\n}' 6 | const warnings = '' 7 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 8 | compiler.compile() 9 | expect(compiler.getMachineCode().Warnings).toBe(warnings) 10 | }) 11 | it('should warn: longs used before initialization', () => { 12 | const code = 'struct PLAYER { long sb; } player;\nlong a, b;\nlong message[4];\n\na+=1;\na = message[b];\na = player.sb;\n' 13 | const warnings = "Warning! At line: 5:1. Variable 'a' is used but not initialized.\n |a+=1;\n |^\nWarning! At line: 6:13. Variable 'b' is used but not initialized.\n |a = message[b];\n | ^\nWarning! At line: 7:12. Variable 'player_sb' is used but not initialized.\n |a = player.sb;\n | ^" 14 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 15 | compiler.compile() 16 | expect(compiler.getMachineCode().Warnings).toBe(warnings) 17 | }) 18 | it('should not warn: same as before but initialized', () => { 19 | const code = 'struct PLAYER { long sb; } player;\nlong a, b;\nlong message[4];\n\na=b=1;\na = message[b];\nplayer.sb=message[2];\na = player.sb;\n' 20 | const warnings = '' 21 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 22 | compiler.compile() 23 | expect(compiler.getMachineCode().Warnings).toBe(warnings) 24 | }) 25 | it('should compile with warning: unused global variable', () => { 26 | const code = 'long la=0, lb; fixed fa, fb=0.0; fa = fb - (fixed)la;' 27 | const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare f100000000\n^const SET @f100000000 #0000000005f5e100\n^declare la\n^declare lb\n^declare fa\n^declare fb\n\nCLR @la\nCLR @fb\nSET @r0 $la\nMUL @r0 $f100000000\nSET @fa $fb\nSUB @fa $r0\nFIN\n' 28 | const warnings = "Warning! At line: 1:12. Unused variable 'lb'.\n |long la=0, lb; fixed fa, fb=0.0; fa = fb - (fixed)la;\n | ^" 29 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 30 | compiler.compile() 31 | expect(compiler.getAssemblyCode()).toBe(assembly) 32 | expect(compiler.getMachineCode().Warnings).toBe(warnings) 33 | }) 34 | it('should warn: Variable not used inside a function', () => { 35 | const code = `search(2); 36 | struct PLAYER { 37 | long address, balance, VDLS; 38 | } *playerPtr, players[2]; 39 | struct PLAYER * search(long playerAddress) { 40 | long nPlayers=0; 41 | struct PLAYER * foundPlayer; 42 | playerPtr = &players[0]; 43 | for (long auxI = 0; auxI < nPlayers; auxI++) { 44 | if (playerPtr->address == playerAddress) { 45 | return playerPtr; 46 | } 47 | playerPtr += (sizeof(struct PLAYER)); 48 | playerPtr = playerPtr + (sizeof(struct PLAYER)); 49 | playerPtr++; 50 | } 51 | return NULL; 52 | }` 53 | const warnings = `Warning! At line: 7:21. Unused variable 'foundPlayer'. 54 | | struct PLAYER * foundPlayer; 55 | | ^` 56 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 57 | compiler.compile() 58 | expect(compiler.getMachineCode().Warnings).toBe(warnings) 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /samples/ConfigurableTimer.md: -------------------------------------------------------------------------------- 1 | **DEPRECATION NOTICE:** 2 | 3 | This contract may be or not be compatible with SmartC version greater or equal 2 because Signum Rainbow Hard Fork broke some compatibilities. Test before use or convert the API calls to new built-in functions. 4 | 5 | # Configurable timer 6 | The contract will act as a timer for any incoming transactions, dispatching all balance to target address on next block that is multiple of a configurable number. Creator must configure target address with a message. 7 | 8 | ## How to use 9 | * Deploy the contract 10 | * Creator must send a binary message with the address of target (swap endianess 8-bytes for text message) 11 | * At any incoming transaction the balance will be forwarded to target address on next block that is multiple of the chosen number. 12 | 13 | ## Configure target on deployment 14 | This would make the contract much simpler. The problem is that when I'm deploying a contract that depends of other to be its timer, it is not possible to know the target address before creating the contract. So I can: 15 | * deploy timer contract 16 | * get timer contract address 17 | * add timer address into the main contract and deploy the main contract 18 | * get main contract address 19 | * set up the target of timer contract to be the main contract 20 | 21 | ## Deployments 22 | * `TS-ETGR-9HEF-KZG2-BQ6Y6` in Testnet. 23 | 24 | ## Source code 25 | ```c 26 | #define MULTIPLE 5 27 | 28 | #program name MultipleOf 29 | #program description Creator deploys the contract and send a binary\ 30 | message with target address. The contract will act as a timer for\ 31 | any incoming transactions, dispatching all balance to target address on\ 32 | next block that is multiple of MULTIPLE. 33 | #program activationAmount 1_1000_0000 34 | 35 | /* Do not change below here */ 36 | 37 | #include APIFunctions 38 | #pragma version 1.0 39 | 40 | const long multiple = MULTIPLE, n32 = 32; 41 | long lastTimestamp, sleepBlocks, creator; 42 | 43 | B_To_Address_Of_Creator(); 44 | creator = Get_B1(); 45 | 46 | // phase 1: wait to receive target address from creator 47 | do { 48 | A_To_Tx_After_Timestamp(lastTimestamp); 49 | if (Get_A1() == 0) { 50 | halt; 51 | continue; 52 | } 53 | lastTimestamp = Get_Timestamp_For_Tx_In_A(); 54 | B_To_Address_Of_Tx_In_A(); 55 | if (Get_B1() == creator) { 56 | Message_From_Tx_In_A_To_B(); 57 | break; 58 | } 59 | } while (true); 60 | 61 | // phase 2: Endless timer transaction 62 | do { 63 | sleepBlocks = multiple - ((Get_Block_Timestamp() >> 32) % multiple); 64 | if (sleepBlocks != multiple) { 65 | sleep sleepBlocks; 66 | } 67 | Send_All_To_Address_In_B(); 68 | } while (true); 69 | ``` 70 | 71 | ## Tests for simulator 72 | 73 | * Block 3 -> Activation setting target address to TS-XL84-VQCD-BGFR-EUHW8 (id 14083313339354499266 = 0xC371FADD94BEC8C2 = "messageHex": "c2c8be94ddfa71c3"). Verify message sent to target on block 5. 74 | * Block 11 -> Sending transaction to activate contract. Verify message sent to target on block 15. 75 | * Block 17 -> Sending transaction to activate contract. Verify message sent to target on block 20. 76 | * Block 23 -> Sending transaction to activate contract. Verify message sent to target on block 25. 77 | * Block 29 -> Sending transaction to activate contract. Verify message sent to target on block 30. 78 | * Block 35 -> Sending transaction to activate contract. Verify message sent to target on block 40. 79 | 80 | ```json 81 | [ 82 | {"sender": "555n","recipient": "999n","amount": "2_0000_0000n","blockheight": 3, "messageHex": "c2c8be94ddfa71c3"}, 83 | 84 | {"sender": "0xAABBn","recipient": "999n","amount": "2_0000_0000n","blockheight": 11}, 85 | {"sender": "0xAABBn","recipient": "999n","amount": "2_0000_0000n","blockheight": 17}, 86 | {"sender": "0xAABBn","recipient": "999n","amount": "2_0000_0000n","blockheight": 23}, 87 | {"sender": "0xAABBn","recipient": "999n","amount": "2_0000_0000n","blockheight": 29}, 88 | {"sender": "0xAABBn","recipient": "999n","amount": "2_0000_0000n","blockheight": 35} 89 | ] 90 | ``` 91 | -------------------------------------------------------------------------------- /src/parser/parser.ts: -------------------------------------------------------------------------------- 1 | import { CONTRACT } from '../typings/contractTypes' 2 | import { PRE_TOKEN, TOKEN, TOKEN_TYPES } from '../typings/syntaxTypes' 3 | 4 | /** Translate an array of pre tokens to an array of tokens. First phase of parsing. 5 | * @param tokens Array of pre-tokens 6 | * @returns Array of TOKENS. Recursive on Arr, CodeCave and CodeDomain types 7 | * @throws {Error} at any mistakes 8 | */ 9 | export default function parser (Program: CONTRACT, preTokens: PRE_TOKEN[]): TOKEN[] { 10 | let mainLoopIndex: number 11 | const startOfSearch: PRE_TOKEN[] = [] 12 | 13 | function getNextRawToken () { 14 | mainLoopIndex++ 15 | return preTokens[mainLoopIndex - 1] 16 | } 17 | // Will run once 18 | function parseMain () : TOKEN[] { 19 | const tokenTrain: TOKEN[] = [] 20 | mainLoopIndex = 0 21 | while (mainLoopIndex < preTokens.length) { 22 | tokenTrain.push(getNextToken()) 23 | } 24 | return tokenTrain 25 | } 26 | 27 | // Process element preTokens started at position mainLoopIndex (outer scope) and returns a functional token 28 | function getNextToken () { 29 | const CurrentPreToken = getNextRawToken() 30 | let RetToken: TOKEN 31 | 32 | if (CurrentPreToken.type !== 'PreToken') { 33 | return CurrentPreToken 34 | } 35 | // take care of recursive tokens 36 | switch (CurrentPreToken.value) { 37 | case ']': 38 | case ')': 39 | case '}': 40 | if (startOfSearch.length > 0) { 41 | const startingToken = startOfSearch.pop() 42 | throw new Error(Program.Context.formatError(CurrentPreToken.line, 43 | `Expecting to close the '${startingToken?.value}' ` + 44 | `started at line ${startingToken?.line}, but found '${CurrentPreToken.value}'.`)) 45 | } 46 | throw new Error(Program.Context.formatError(CurrentPreToken.line, 47 | `Unexpected closing '${CurrentPreToken.value}'.`)) 48 | case '[': 49 | startOfSearch.push(CurrentPreToken) 50 | RetToken = { type: 'Arr', value: '', precedence: 0, line: CurrentPreToken.line } 51 | RetToken.params = getTokensUntil(']', RetToken.type, RetToken.line) 52 | return RetToken 53 | case '(': 54 | startOfSearch.push(CurrentPreToken) 55 | if (mainLoopIndex > 1 && preTokens[mainLoopIndex - 2].type === 'Variable') { 56 | RetToken = { type: 'Function', value: '', precedence: 0, line: CurrentPreToken.line } 57 | } else { 58 | RetToken = { type: 'CodeCave', value: '', precedence: 0, line: CurrentPreToken.line } 59 | } 60 | RetToken.params = getTokensUntil(')', RetToken.type, RetToken.line) 61 | return RetToken 62 | case '{': 63 | startOfSearch.push(CurrentPreToken) 64 | RetToken = { type: 'CodeDomain', value: '', precedence: 0, line: CurrentPreToken.line } 65 | RetToken.params = getTokensUntil('}', RetToken.type, RetToken.line) 66 | return RetToken 67 | default: 68 | throw new Error(Program.Context.formatError(CurrentPreToken.line, 69 | `Unexpected token '${CurrentPreToken.value}' - type: '${CurrentPreToken.type}'.`)) 70 | } 71 | } 72 | 73 | // Process element preTokens started at position mainLoopIndex (outer scope) and returns a array of tokens 74 | // until endChar is found 75 | function getTokensUntil (endChar: ')'|'}'|']', parentType: TOKEN_TYPES, line: string) : TOKEN [] { 76 | const returnedTokens : TOKEN [] = [] 77 | if (mainLoopIndex >= preTokens.length) { 78 | throw new Error(Program.Context.formatError(line, 79 | `End of file reached while searching for closing '${endChar}'.`)) 80 | } 81 | while (preTokens[mainLoopIndex].value !== endChar) { 82 | returnedTokens.push(getNextToken()) 83 | // getNextToken will increase mainLoopIndex for loop 84 | if (mainLoopIndex >= preTokens.length) { 85 | throw new Error(Program.Context.formatError(line, 86 | `End of file reached while searching for closing '${endChar}'.`)) 87 | } 88 | } 89 | startOfSearch.pop() 90 | // discard closing char 91 | mainLoopIndex++ 92 | return returnedTokens 93 | } 94 | 95 | return parseMain() 96 | } 97 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [v2.3](https://github.com/deleterium/SmartC/tree/v2.3) (2024-12-08) 2 | 3 | [Commits](https://github.com/deleterium/SmartC/commits/v2.3) 4 | * Feat: More detailed warnings and errors. 5 | * New keyword 'register' as modifier for variables. They are scoped and use a free register as variable. 6 | * Refactored preprocessor and code generation. 7 | 8 | ## [v2.2](https://github.com/deleterium/SmartC/tree/v2.2) (2024-02-12) 9 | 10 | [Commits](https://github.com/deleterium/SmartC/commits/v2.2) 11 | - Added getAccountBalance() support by @Wekuz/@deleterium 12 | - Compiler now warns when using not initialized variables 13 | - Changed strategy for functions passing and returning values (saving use of user stack) 14 | - New keyword `inline` for functions declaration (saving use of code stack) 15 | 16 | ## New Contributors 17 | * @Wekuz made their first contribution in https://github.com/deleterium/SmartC/pull/31 18 | 19 | ## [v2.1](https://github.com/deleterium/SmartC/tree/v2.1) (2022-08-06) 20 | 21 | [Commits](https://github.com/deleterium/SmartC/commits/v2.1) 22 | - Fix on **severe issue in sendQuantity** built-in function (issue #24) 23 | - Fix implementation for optimization level 3 (should be stable now) 24 | - New contract **samples LiquidityPool** and **WheelOfSigna** 25 | - New built-in functions `readShortMessage` and `sendShortMessage` to optimize code for short messages 26 | - Other minor bug fixes and code de-smelling 27 | 28 | ## [v2.0](https://github.com/deleterium/SmartC/tree/v2.0) (2022-07-03) 29 | 30 | [Commits](https://github.com/deleterium/SmartC/commits/v2.0) 31 | 32 | - **Support all new features from Signum Rainbow Hard Fork** 33 | - New 42 built-in functions: **easy use of Signum API** 34 | - Documentation updated with refactored examples, devs must read it again 35 | - **Fixed point numbers** to handle balance, much easier in calculations 36 | - Checks for **type castings** are stronger and issuing warnings for implicit conversions 37 | - Optimization level 3 uses VM to trace variables values (beta version, not default) 38 | - Showing many errors after failed compilations (if possible) 39 | - Many changes in `#pragma` and `#program` to allow **integration with SC-Simulator** 40 | 41 | ## [v1.0](https://github.com/deleterium/SmartC/tree/v1.0) (2022-01-16) 42 | 43 | [Commits](https://github.com/deleterium/SmartC/commits/v1.0) 44 | 45 | - Massive code refactoring, decreasing code cognitive complexity. More than 80 commits 46 | - Changed strategy for code optimization. Better and safer 47 | - Added **switch :: case :: default** statement 48 | - Added **sizeof** keyword 49 | - Allow use of array of structs 50 | 51 | ## [v0.3](https://github.com/deleterium/SmartC/tree/v0.3) (2021-10-16) 52 | 53 | [Commits](https://github.com/deleterium/SmartC/commits/v0.3) 54 | 55 | - Source code refactored from Javascript to Typescript 56 | - Included inline comments on interfaces and types 57 | - Massive improvement on compiler error messages 58 | - Functions can return struct pointer 59 | - Recursive functions 60 | - Support for using functions with modifiers `Array` or `Member` Ex: *a = test()->next;* 61 | - Improved rules for pointer variables verification 62 | - Added void pointer variables 63 | - Added property **.length** to get array size 64 | - Special function **void catch(void)** to handle execution exceptions 65 | - Macro 'userStackPages' and 'codeStackPages' for fine tuning memory parameters 66 | - Struct can have recursive definition of its pointer 67 | - More optimizations on constant variables (thru variables named nNUMBER) 68 | - Added Machine Code Hash Information 69 | - Improved preprocessor with **#ifdef**, **#ifndef**, **#else**, **#endif** directives 70 | - Copy to clipboard button for easy use with SC-Simulator 71 | - Added macro 'outputSourceLineNumber' to add verbosity in assembly generated code 72 | - Project integration with SonarCloud and fix security vulnerabilities in regex expressions 73 | - Increased test coverage for wrong source code 74 | 75 | 76 | ## [v0.2](https://github.com/deleterium/SmartC/tree/v0.2) (2021/07/23) 77 | 78 | [Commits](https://github.com/deleterium/SmartC/commits/v0.2) 79 | 80 | - Syntax highlight for source code textarea 81 | - Fine tuning globalOptimization 82 | - Added **#define** and **#undef** preprocessor directives. 83 | 84 | ## [v0.1](https://github.com/deleterium/SmartC/tree/v0.1) (2021/06/30) 85 | 86 | [Commits](https://github.com/deleterium/SmartC/commits/v0.1) 87 | 88 | - Initial working release 89 | 90 | ## v0 (2021/04/06) 91 | 92 | 93 | - [Initial release](https://github.com/deleterium/SmartC/tree/bb3edafcf0d3db0153201b594157555d686a9962) in Github 94 | -------------------------------------------------------------------------------- /samples/XmasContest.smartc.c: -------------------------------------------------------------------------------- 1 | /* DEPRECATION NOTICE: 2 | * This contract may be not compatible with SmartC version greater or equal 2 3 | * because Signum Rainbow Hard Fork broke some compatibilities. Test before 4 | * use or convert the API calls to new built-in functions. 5 | */ 6 | 7 | #program name XmasContest 8 | #program description Creator adds up balance to the contest, by sending transactions\ 9 | without message. Participants send theirs messages, suposed to be right answer.\ 10 | Creator sends a transaction with a message, that is the right answer. The contract\ 11 | loops thru all messages again comparing the participants messages with the right\ 12 | answer. 10 different participants with the right aswer will receive a prize: 1st\ 13 | 40%, 2nd 25%, 3rd 14%, 4th to 10th 3%. The remaing balance will be donated to SNA,\ 14 | and the contract will finish by sending any upcoming balance to SNA. 15 | #program activationAmount 5_0000_0000 16 | 17 | #pragma globalOptimization 18 | #pragma version 0.3 19 | 20 | #include APIFunctions 21 | 22 | #define FIRST_PRIZE_PC 40 23 | #define SECOND_PRIZE_PC 25 24 | #define THIRD_PRIZE_PC 14 25 | #define FOURTH_TO_TENTH_PRIZE_PC 3 26 | #define SNA_ADDRESS "S-5MS6-5FBY-74H4-9N4HS" 27 | 28 | // Deployed first time with address: S-T7HA-5E8C-PFYL-9E4UE 29 | 30 | struct TXINFO 31 | { 32 | long timestamp; 33 | long sender; 34 | long amount; 35 | long message[4]; 36 | } currentTX; 37 | 38 | struct CHALLENGE { 39 | long rightAnswer[4], 40 | endTimestamp, 41 | winners[10], 42 | prize; 43 | } challenge; 44 | 45 | long winnerCount; 46 | long currentWinnerPrize; 47 | long creator; 48 | 49 | B_To_Address_Of_Creator(); 50 | creator = Get_B1(); 51 | 52 | // Phase 1 53 | // Wait to know challenge aswer. Collect transactions from participants. 54 | do 55 | { 56 | A_To_Tx_After_Timestamp(currentTX.timestamp); 57 | if (Get_A1() == 0) 58 | { 59 | halt; 60 | continue; 61 | } 62 | currentTX.timestamp = Get_Timestamp_For_Tx_In_A(); 63 | B_To_Address_Of_Tx_In_A(); 64 | currentTX.sender = Get_B1(); 65 | if (currentTX.sender == creator) 66 | { 67 | challenge.prize += Get_Amount_For_Tx_In_A(); 68 | Message_From_Tx_In_A_To_B(); 69 | challenge.rightAnswer[0] = Get_B1(); 70 | challenge.rightAnswer[1] = Get_B2(); 71 | challenge.rightAnswer[2] = Get_B3(); 72 | challenge.rightAnswer[3] = Get_B4(); 73 | if (challenge.rightAnswer[0] != 0) 74 | { 75 | // This is not empty message. Consider end of contest! 76 | challenge.endTimestamp = currentTX.timestamp; 77 | } 78 | 79 | } 80 | } while (challenge.endTimestamp == 0); 81 | 82 | // Phase 2 83 | // Cycle thru all messages again 84 | currentTX.timestamp = 0; 85 | while (currentTX.timestamp != challenge.endTimestamp) 86 | { 87 | A_To_Tx_After_Timestamp(currentTX.timestamp); 88 | getTxDetails(); 89 | if (currentTX.sender == creator || isWinner(currentTX.sender) ) 90 | { 91 | continue; 92 | } 93 | if (currentTX.message[0] == challenge.rightAnswer[0] && 94 | currentTX.message[1] == challenge.rightAnswer[1] && 95 | currentTX.message[2] == challenge.rightAnswer[2] && 96 | currentTX.message[3] == challenge.rightAnswer[3] && 97 | winnerCount < 10) 98 | { 99 | // We have a winner! 100 | if (winnerCount == 0) { 101 | currentWinnerPrize = challenge.prize * FIRST_PRIZE_PC / 100; 102 | } else if (winnerCount == 1) { 103 | currentWinnerPrize = challenge.prize * SECOND_PRIZE_PC / 100; 104 | } else if (winnerCount == 2) { 105 | currentWinnerPrize = challenge.prize * THIRD_PRIZE_PC / 100; 106 | } else { 107 | currentWinnerPrize = challenge.prize * FOURTH_TO_TENTH_PRIZE_PC / 100; 108 | } 109 | Set_B1(currentTX.sender); 110 | Send_To_Address_In_B(currentWinnerPrize); 111 | challenge.winners[winnerCount] = currentTX.sender; 112 | winnerCount++; 113 | } 114 | } 115 | 116 | // Phase 3 117 | // Contest end. Any remaining/incoming value to SNA 118 | Clear_B(); 119 | Set_B1(SNA_ADDRESS); 120 | while (true) 121 | { 122 | Send_All_To_Address_In_B(); 123 | } 124 | 125 | long isWinner(long address) 126 | { 127 | long i; 128 | for (i = 0; i < winnerCount; i++) { 129 | if (challenge.winners[i] == address) 130 | { 131 | return true; 132 | } 133 | } 134 | return false; 135 | } 136 | 137 | void getTxDetails(void) 138 | { 139 | currentTX.amount = Get_Amount_For_Tx_In_A(); 140 | currentTX.timestamp = Get_Timestamp_For_Tx_In_A(); 141 | Message_From_Tx_In_A_To_B(); 142 | currentTX.message[0] = Get_B1(); 143 | currentTX.message[1] = Get_B2(); 144 | currentTX.message[2] = Get_B3(); 145 | currentTX.message[3] = Get_B4(); 146 | B_To_Address_Of_Tx_In_A(); 147 | currentTX.sender = Get_B1(); 148 | } 149 | -------------------------------------------------------------------------------- /src/codeGenerator/assemblyProcessor/typeCastingToAsm.ts: -------------------------------------------------------------------------------- 1 | import { CONTRACT } from '../../typings/contractTypes' 2 | import { DECLARATION_TYPES } from '../../typings/syntaxTypes' 3 | import { GENCODE_SOLVED_OBJECT } from '../codeGeneratorTypes' 4 | import utils from '../utils' 5 | import { createSimpleInstruction, toRegister } from './createInstruction' 6 | 7 | export function typeCasting ( 8 | Program: CONTRACT, InSolved: GENCODE_SOLVED_OBJECT, toType: DECLARATION_TYPES, line: string 9 | ) : GENCODE_SOLVED_OBJECT { 10 | const fromType = utils.getDeclarationFromMemory(InSolved.SolvedMem) 11 | if (fromType === toType) { 12 | return InSolved 13 | } 14 | 15 | function typeCastingMain () : GENCODE_SOLVED_OBJECT { 16 | switch (toType) { 17 | case 'void': 18 | // From anything to void 19 | Program.Context.freeRegister(InSolved.SolvedMem.address) 20 | InSolved.SolvedMem = utils.createVoidMemObj() 21 | return InSolved 22 | case 'long': 23 | return toLong() 24 | case 'fixed': 25 | return toFixed() 26 | case 'void_ptr': 27 | case 'long_ptr': 28 | case 'fixed_ptr': 29 | case 'struct_ptr': 30 | return toPointer() 31 | case 'struct': 32 | default: 33 | throw new Error('Internal error') 34 | } 35 | } 36 | 37 | function toFixed () : GENCODE_SOLVED_OBJECT { 38 | switch (fromType) { 39 | case 'long': 40 | // From long to fixed 41 | InSolved = toRegister(Program, InSolved, line) 42 | InSolved.asmCode += createSimpleInstruction('LongToFixed', InSolved.SolvedMem.asmName) 43 | utils.setMemoryDeclaration(InSolved.SolvedMem, 'fixed') 44 | return InSolved 45 | case 'void': 46 | // From void to fixed 47 | InSolved.SolvedMem = Program.Context.getNewRegister(line) 48 | InSolved.asmCode += `CLR @${InSolved.SolvedMem.asmName}\n` 49 | utils.setMemoryDeclaration(InSolved.SolvedMem, 'fixed') 50 | return InSolved 51 | case 'struct': 52 | case 'void_ptr': 53 | case 'long_ptr': 54 | case 'fixed_ptr': 55 | case 'struct_ptr': 56 | throw new Error(Program.Context.formatError(line, `It is not possible to cast ${fromType} to fixed number.`)) 57 | default: 58 | throw new Error('Internal error') 59 | } 60 | } 61 | 62 | function toLong () : GENCODE_SOLVED_OBJECT { 63 | switch (fromType) { 64 | case 'void': 65 | // From void to long 66 | InSolved.SolvedMem = Program.Context.getNewRegister(line) 67 | InSolved.asmCode += `CLR @${InSolved.SolvedMem.asmName}\n` 68 | utils.setMemoryDeclaration(InSolved.SolvedMem, 'long') 69 | return InSolved 70 | case 'fixed': 71 | InSolved = toRegister(Program, InSolved, line) 72 | InSolved.asmCode += createSimpleInstruction('FixedToLong', InSolved.SolvedMem.asmName) 73 | utils.setMemoryDeclaration(InSolved.SolvedMem, 'long') 74 | return InSolved 75 | case 'struct': 76 | throw new Error(Program.Context.formatError(line, `It is not possible to cast ${fromType} to long number.`)) 77 | case 'void_ptr': 78 | case 'long_ptr': 79 | case 'fixed_ptr': 80 | case 'struct_ptr': 81 | utils.setMemoryDeclaration(InSolved.SolvedMem, 'long') 82 | return InSolved 83 | default: 84 | throw new Error('Internal error') 85 | } 86 | } 87 | 88 | function toPointer () : GENCODE_SOLVED_OBJECT { 89 | switch (fromType) { 90 | case 'void': 91 | // From void to pointer (NULL) 92 | InSolved.SolvedMem = utils.createConstantMemObj(0) 93 | utils.setMemoryDeclaration(InSolved.SolvedMem, toType) 94 | return InSolved 95 | case 'fixed': 96 | // From fixed to any pointer 97 | throw new Error(Program.Context.formatError(line, `It is not possible to cast ${fromType} to a pointer type.`)) 98 | case 'struct': 99 | // From struct to pointer (address of first value in struct) 100 | InSolved.SolvedMem = utils.createConstantMemObj(InSolved.SolvedMem.hexContent) 101 | utils.setMemoryDeclaration(InSolved.SolvedMem, toType) 102 | return InSolved 103 | case 'long': 104 | case 'void_ptr': 105 | case 'long_ptr': 106 | case 'fixed_ptr': 107 | case 'struct_ptr': 108 | // From long to any pointer 109 | // From any pointer to any pointer 110 | utils.setMemoryDeclaration(InSolved.SolvedMem, toType) 111 | return InSolved 112 | default: 113 | throw new Error('Internal error') 114 | } 115 | } 116 | 117 | return typeCastingMain() 118 | } 119 | -------------------------------------------------------------------------------- /src/codeGenerator/codeGeneratorTypes.ts: -------------------------------------------------------------------------------- 1 | import { AST, DECLARATION_TYPES, MEMORY_SLOT } from '../typings/syntaxTypes' 2 | 3 | export type SENTENCE_CONTEXT = { 4 | /** Flag to inform lower level AST that it is declaration sentence */ 5 | isDeclaration: DECLARATION_TYPES 6 | /** Flag to inform lower level AST that it is left side of assignment */ 7 | isLeftSideOfAssignment: boolean 8 | /** Flag to inform lower level AST that it is const declaration sentence */ 9 | isConstSentence: boolean 10 | /** Flag to inform lower level AST that it is register declaration sentence */ 11 | isRegisterSentence: boolean 12 | /** Flag to inform lower level AST that there are an void array assignment */ 13 | hasVoidArray: boolean 14 | getAndClearPostOperations(): string 15 | /** Post increment or decrement that shall be included at last */ 16 | postOperations: string 17 | /** Variable reserved for left side assignment. Currently only using for function call 18 | * decision to store or ignore register values */ 19 | leftSideReserved: number 20 | } 21 | 22 | export type GLOBAL_CONTEXT = { 23 | /** Stack saving loops IDs */ 24 | latestLoopId: string[] 25 | /** Auto incrementing index for labels generation */ 26 | jumpId: number 27 | /** Assembly code being created */ 28 | assemblyCode: string 29 | /** Storing warnings from compilation */ 30 | warnings: string[] 31 | /** Errors found */ 32 | errors: string[] 33 | /** Current function being processed */ 34 | currFunctionIndex: number 35 | /** Line counter for source code */ 36 | currSourceLine: number 37 | /** Format errors with line and marks */ 38 | formatError (line: string | undefined, message: string) : string 39 | /** Handle register allocation and liberation in each scope */ 40 | scopedRegisters: string[] 41 | /** Get a new jump id according to current Configs (global scope) */ 42 | getNewJumpID(): string 43 | /** Query the value of last loop id */ 44 | getLatestLoopID(): string 45 | /** Query the value of last loop id that is a pure loop (excluding 'switch' ids) */ 46 | getLatestPureLoopID(): string 47 | /** Helper for debugger to know what are the free registers. */ 48 | printFreeRegisters(): void 49 | /** Operations to start a new scope for registers */ 50 | startScope(arg :string): void 51 | /** Operations to close a scope for registers */ 52 | stopScope(arg :string): void 53 | /** Auxiliary variables used as registers */ 54 | registerInfo: { 55 | endurance: 'Standard' | 'Sentence' | 'Scope' 56 | inUse: boolean 57 | Template: MEMORY_SLOT 58 | }[] 59 | /** Verifies if a variable at loc address is register or temporary reused var */ 60 | isTemp(loc: number): boolean 61 | /** Get a new register variable */ 62 | getNewRegister(line?: string): MEMORY_SLOT 63 | /** Informs that variable at loc address can be free */ 64 | freeRegister(loc: number | undefined): void 65 | /** 66 | * Search and return a copy of memory object with name varname. 67 | * Object can be global or local function scope. 68 | * if not found, throws exception with line number. Also sets 'isDeclared' 69 | * to manage use of undeclared variables. 70 | */ 71 | getMemoryObjectByName (varName: string, line?: string, varDeclaration?: DECLARATION_TYPES): MEMORY_SLOT 72 | /** 73 | * Search and return a copy of memory object in addres 'loc'. 74 | * Object can be global or local function scope. 75 | * if not found, throws exception with line number. 76 | */ 77 | getMemoryObjectByLocation (loc: number|bigint|string, line?: string): MEMORY_SLOT 78 | /** Options to be used in each sentence */ 79 | SentenceContext: SENTENCE_CONTEXT 80 | /** Use tokenizer loop to detect some properties */ 81 | TokenizerDetection: { 82 | hasFixed: boolean, 83 | hasAutoCounter: boolean 84 | } 85 | ShaperContext: { 86 | latestLoopId: string[], 87 | isFunctionArgument: boolean, 88 | currentScopeName: string, 89 | currentPrefix: string 90 | } 91 | } 92 | 93 | export type GENCODE_ARGS = { 94 | /** AST to traverse */ 95 | RemAST: AST, 96 | /** true if wanted return object to be suitable for logical operations */ 97 | logicalOp: boolean, 98 | /** true if wanted to reverse logic for logical operations */ 99 | revLogic: boolean, 100 | /** Label to jump if logical operation is false */ 101 | jumpFalse?: string, 102 | /** Label to jump if logical operatio is true */ 103 | jumpTrue?: string 104 | } 105 | 106 | export type GENCODE_SOLVED_OBJECT = { 107 | /** Memory object representing solved AST */ 108 | SolvedMem: MEMORY_SLOT 109 | /** Assembly sourcecode needed to solve AST */ 110 | asmCode: string 111 | } 112 | 113 | export type FLATTEN_MEMORY_RETURN_OBJECT = { 114 | FlatMem: MEMORY_SLOT 115 | asmCode: string 116 | isNew: boolean 117 | } 118 | -------------------------------------------------------------------------------- /debug.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 64 | 65 | 66 | Simple debugger page 67 | 98 | 99 | 100 | 101 |
Simple debugger page
102 |

Use this page to debug or inspect SmartC process, using a browser.

103 |
104 |
105 |
106 |
107 | 108 | 109 |
110 |
111 |

112 |                 

113 |                 

114 |             
115 |
116 |
117 | 118 | -------------------------------------------------------------------------------- /samples/get3random.smartc.c: -------------------------------------------------------------------------------- 1 | /* DEPRECATION NOTICE: 2 | * This contract may be not compatible with SmartC version greater or equal 2 3 | * because Signum Rainbow Hard Fork broke some compatibilities. Test before 4 | * use or convert the API calls to new built-in functions. 5 | */ 6 | 7 | #program name Get3Random 8 | #program description When a message arrives, the program tries to parse a number\ 9 | from message. Number must be bigger or equal 5 and lower or equal 9999999. If\ 10 | found, then program mixes random hash for 3 blocks and send back a message\ 11 | to the sender with the random numbers without repetition. When there is no more\ 12 | pending messages, all balance remaining is sent to program creator. 13 | #program activationAmount 1_0000_0000 14 | 15 | #pragma version 0.2 16 | #pragma maxAuxVars 2 17 | #pragma maxConstVars 2 18 | #pragma globalOptimization 19 | 20 | #include APIFunctions 21 | 22 | struct TXINFO 23 | { 24 | long timestamp; 25 | long sender; 26 | long amount; 27 | } currentTX; 28 | 29 | long i, userNumber, result_1, result_2, result_3, result_4; 30 | const long n8=8, n10=10, n0xff=0xff; 31 | 32 | B_To_Address_Of_Creator(); 33 | long CREATOR = Get_B1(); 34 | 35 | while (true) { 36 | 37 | // Loop all incoming TX 38 | for (A_To_Tx_After_Timestamp(currentTX.timestamp); Get_A1() != 0; A_To_Tx_After_Timestamp(currentTX.timestamp) ) { 39 | 40 | // Get TX details 41 | currentTX.amount = Get_Amount_For_Tx_In_A(); 42 | currentTX.timestamp = Get_Timestamp_For_Tx_In_A(); 43 | Message_From_Tx_In_A_To_B(); 44 | userNumber = atoi(Get_B1()); 45 | B_To_Address_Of_Tx_In_A(); 46 | currentTX.sender = Get_B1(); 47 | 48 | if (userNumber < 5) { 49 | // Send an error message 50 | Set_A1_A2("Please s","end a va"); 51 | Set_A3_A4("lue >= 5", 0); 52 | Send_A_To_Address_In_B(); 53 | // Return any excess balance given 54 | if (currentTX.amount > 0) 55 | Send_To_Address_In_B(currentTX.amount); 56 | // Proceed to next message. 57 | continue; 58 | } 59 | 60 | // Draw mixing randomness of 3 blocks 61 | Clear_A_And_B(); 62 | i = 0; 63 | do { 64 | do { 65 | if (i != 0) 66 | sleep 1; 67 | Put_Last_Block_Hash_In_A(); 68 | XOR_B_with_A(); 69 | i++; 70 | } while (i <= 2); 71 | 72 | // Get 4 random numbers between 1 and userNumber 73 | result_1 = ((Get_B1() >> 2 ) % userNumber) + 1; 74 | result_2 = ((Get_B2() >> 2 ) % userNumber) + 1; 75 | result_3 = ((Get_B3() >> 2 ) % userNumber) + 1; 76 | result_4 = ((Get_B4() >> 2 ) % userNumber) + 1; 77 | // Try to avoid a new round using 4th number 78 | if (result_1 == result_2) 79 | result_1 = result_4; 80 | else if (result_1 == result_3) 81 | result_1 = result_4; 82 | else if (result_2 == result_3) 83 | result_2 = result_4; 84 | // Repeat process next block if still there are repeated numbers. 85 | } while (result_1 == result_2 || result_1 == result_3 || result_2 == result_3); 86 | 87 | // Send message with draw numbers 88 | Set_B1(currentTX.sender); 89 | Set_A1_A2("Draw: ", itoa_plus(result_1)); 90 | Set_A3_A4(itoa_plus(result_2), itoa_plus(result_3)); 91 | Send_A_To_Address_In_B(); 92 | Send_To_Address_In_B(currentTX.amount); 93 | 94 | } 95 | 96 | // Send all remaining balance to creator and freeze contract until next message 97 | Set_B1(CREATOR); 98 | Send_All_To_Address_In_B(); 99 | } 100 | 101 | 102 | /* ************** Library functions **************************** */ 103 | 104 | // Iterative function to implement atoi() function in C 105 | // Expects a long containing a string. If any byte is not a char numeric 106 | // representation, then stop and return. Only positive numbers, decimal, 107 | // and integers are converted. Returns zero if no number was processed. 108 | long atoi(long val) 109 | { 110 | long ret = 0, chr; 111 | do { 112 | chr = (0xff & val) - '0'; 113 | if (chr < 0 || chr >= n10) 114 | break; 115 | ret *= n10; 116 | ret += chr; 117 | val >>= n8; 118 | } while (1); 119 | return ret; 120 | } 121 | 122 | // Iterative function to implement itoa() function in C 123 | // Expects a long. If number is negative or bigger than MAX_STRING 124 | // (it will not fit in a long), returns long meaning "#error". 125 | // Pad beginning with spaces to allow easy concatenation 126 | long itoa_plus(long val) 127 | { 128 | long ret = " "; 129 | if (val == 0) { 130 | return (ret << n8) + '0'; 131 | } 132 | 133 | if (val > 0 && val <= 9999999) { 134 | do { 135 | if (val == 0) { 136 | return ret; 137 | } 138 | ret <<= n8; 139 | ret += '0' + val % n10; 140 | val /= n10; 141 | } while (1); 142 | } 143 | return " #error"; 144 | } 145 | -------------------------------------------------------------------------------- /src/smartc.ts: -------------------------------------------------------------------------------- 1 | import preprocessor from './preprocessor/preprocessor' 2 | import tokenizer from './tokenizer/tokenizerV3' 3 | import parser from './parser/parser' 4 | import shaper from './shaper/shaper' 5 | import syntaxProcessor from './syntaxProcessor/syntaxProcessor' 6 | import codeGenerator from './codeGenerator/codeGenerator' 7 | import assembler from './assembler/assembler' 8 | 9 | import { PRE_TOKEN, TOKEN } from './typings/syntaxTypes' 10 | import { CONTRACT, MACHINE_OBJECT } from './typings/contractTypes' 11 | import { createContext } from './context' 12 | 13 | /** 14 | * SmartC Compiler class. 15 | * 16 | * SmartC can compile C code or Assembly code for signum blockchain. Choose desired language with 17 | * argument options. Method `compile()` will compile entire code. If something wrong occurs, it will throw error. 18 | * Get the compiled instructions with methods `getAssemblyCode` or `getMachineCode` 19 | * 20 | * Example: Simple compilation test 21 | * ```ts 22 | * try { 23 | * const startUpTest = new SmartC({ 24 | * language: 'C', 25 | * sourceCode: '#pragma maxAuxVars 1\nlong a, b, c; a=b/~c;' 26 | * }) 27 | * startUpTest.compile() 28 | * const assemblyText = startUpTest.getAssemblyCode() 29 | * const machineObject = startUpTest.getMachineCode() 30 | * // Do something 31 | * } catch (e) { 32 | * return "Compilation error: " + e.message 33 | * } 34 | *``` 35 | * @module SmartC 36 | */ 37 | export class SmartC { 38 | private readonly language 39 | private readonly sourceCode 40 | private preAssemblyCode?: string 41 | private MachineCode?: MACHINE_OBJECT 42 | // @ts-ignore 43 | private Program: CONTRACT = { 44 | sourceLines: [], 45 | Global: { 46 | BuiltInFunctions: [], 47 | APIFunctions: [], 48 | macros: [], 49 | sentences: [] 50 | }, 51 | functions: [], 52 | memory: [], 53 | typesDefinitions: [], 54 | // Default configuration for compiler 55 | Config: { 56 | compilerVersion: '9999.9.6', 57 | maxAuxVars: 3, 58 | maxConstVars: 0, 59 | optimizationLevel: 2, 60 | reuseAssignedVar: true, 61 | APIFunctions: false, 62 | fixedAPIFunctions: false, 63 | PName: '', 64 | PDescription: '', 65 | PActivationAmount: '', 66 | PCreator: '', 67 | PContract: '', 68 | PUserStackPages: 0, 69 | PCodeStackPages: 0, 70 | PCodeHashId: '', 71 | verboseAssembly: false, 72 | verboseScope: false 73 | } 74 | } 75 | 76 | constructor (Options: { language: 'C' | 'Assembly', sourceCode: string }) { 77 | this.Program.sourceLines = Options.sourceCode.split('\n') 78 | this.language = Options.language 79 | this.sourceCode = Options.sourceCode 80 | this.Program.Context = createContext(this.Program) 81 | } 82 | 83 | /** 84 | * Triggers compilation process 85 | * @throws {Error} if compilation is not sucessfull */ 86 | compile () : this { 87 | let preprocessed : string 88 | let tokenized: PRE_TOKEN[] 89 | let parsed: TOKEN[] 90 | if (this.MachineCode) { 91 | return this 92 | } 93 | switch (this.language) { 94 | case 'C': 95 | preprocessed = preprocessor(this.Program) 96 | tokenized = tokenizer(this.Program, preprocessed) 97 | parsed = parser(this.Program, tokenized) 98 | shaper(this.Program, parsed) 99 | syntaxProcessor(this.Program) 100 | this.preAssemblyCode = codeGenerator(this.Program) 101 | break 102 | case 'Assembly': 103 | this.preAssemblyCode = this.sourceCode 104 | break 105 | default: 106 | throw new Error('Invalid usage. Language must be "C" or "Assembly".') 107 | } 108 | this.MachineCode = assembler(this.preAssemblyCode) 109 | this.MachineCode.Warnings = this.Program.Context.warnings.join('\n') 110 | return this 111 | } 112 | 113 | /** 114 | * @returns Sucessfull compiled assembly code 115 | * @throws {Error} if compilation was not done 116 | */ 117 | getAssemblyCode () : string { 118 | if (!this.MachineCode) { 119 | throw new Error('Source code was not compiled.') 120 | } 121 | return this.MachineCode.AssemblyCode 122 | } 123 | 124 | /** 125 | * @returns Sucessfull compiled machine code 126 | * @throws {Error} if compilation was not done 127 | */ 128 | getMachineCode () : MACHINE_OBJECT { 129 | if (!this.MachineCode) { 130 | throw new Error('Source code was not compiled.') 131 | } 132 | return this.MachineCode 133 | } 134 | 135 | /** 136 | * Ask for current compiler version 137 | * @returns compiler version string 138 | */ 139 | getCompilerVersion () : string { 140 | return this.Program.Config.compilerVersion 141 | } 142 | } 143 | 144 | if (typeof window !== 'undefined' && typeof window.document !== 'undefined') { 145 | // @ts-ignore: Browser only 146 | window.SmartC = SmartC 147 | } 148 | -------------------------------------------------------------------------------- /src/codeGenerator/assemblyProcessor/optimizerVM/index.ts: -------------------------------------------------------------------------------- 1 | import { CPU } from './cpu' 2 | 3 | export const unknownValue = -1n 4 | 5 | // TODO: Add stack inspection. It will be needed to save the line of the PUSH, 6 | // so optimizing POP also delete the respective PUSH. 7 | /** 8 | * Object for memory entries 9 | * 10 | * @member {string} varName Variable name defined in assembly 11 | * @member {bigint} value Variable value (64-bit unsigned) 12 | * @member {string} shadow If content is the same as other variable, 13 | * this will have the other variable name. 14 | */ 15 | export interface MemoryObj { 16 | varName: string 17 | value: bigint 18 | shadow: string 19 | } 20 | 21 | export const Constants = { 22 | // do not change these 23 | minus1: 18446744073709551615n, // -1 in 64-bit unsigned 24 | pow2to64: 18446744073709551616n, // 2^64 25 | maxPositive: 9223372036854775807n, // (2^63)-1 26 | numberMaxPositive: 9223372036854775000 27 | } 28 | 29 | export const utils = { 30 | unsigned2signed (unsigned: bigint): bigint { 31 | unsigned %= 1n << 64n 32 | if (unsigned >= (1n << 63n)) { 33 | return unsigned - (1n << 64n) 34 | } 35 | return unsigned 36 | }, 37 | 38 | signed2unsigned (signed: bigint): bigint { 39 | signed %= 18446744073709551616n 40 | if (signed < 0) { 41 | return signed + 18446744073709551616n 42 | } 43 | return signed 44 | } 45 | } 46 | 47 | export class CONTRACT { 48 | Memory: MemoryObj[] 49 | asmCodeArr: string[] 50 | 51 | constructor (asmCode: string[]) { 52 | this.Memory = [] 53 | this.asmCodeArr = asmCode 54 | CPU.cpuDeploy(this) 55 | this.Memory.push( 56 | { varName: 'A1', value: unknownValue, shadow: '' }, 57 | { varName: 'A2', value: unknownValue, shadow: '' }, 58 | { varName: 'A3', value: unknownValue, shadow: '' }, 59 | { varName: 'A4', value: unknownValue, shadow: '' }, 60 | { varName: 'B1', value: unknownValue, shadow: '' }, 61 | { varName: 'B2', value: unknownValue, shadow: '' }, 62 | { varName: 'B3', value: unknownValue, shadow: '' }, 63 | { varName: 'B4', value: unknownValue, shadow: '' }) 64 | } 65 | 66 | unknownMemory (keepRegisters: boolean, keepSuperRegisters: boolean) : void { 67 | this.Memory.forEach((Mem) => { 68 | if (keepRegisters && /^r\d$/.test(Mem.varName)) { 69 | return 70 | } 71 | if (keepSuperRegisters && /^[AB][1234]$/.test(Mem.varName)) { 72 | return 73 | } 74 | if (/^n\d+$/.test(Mem.varName)) { 75 | Mem.value = BigInt(Mem.varName.substring(1)) 76 | Mem.shadow = '' 77 | return 78 | } 79 | Mem.value = unknownValue 80 | Mem.shadow = '' 81 | }) 82 | } 83 | 84 | unknownSuperRegisterA () : void { 85 | this.Memory.forEach((Mem) => { 86 | if (/^A[1234]$/.test(Mem.varName)) { 87 | Mem.value = unknownValue 88 | Mem.shadow = '' 89 | return 90 | } 91 | if (/^A[1234]$/.test(Mem.shadow)) { 92 | Mem.shadow = '' 93 | } 94 | }) 95 | } 96 | 97 | unknownSuperRegisterB () : void { 98 | this.Memory.forEach((Mem) => { 99 | if (/^B[1234]$/.test(Mem.varName)) { 100 | Mem.value = unknownValue 101 | Mem.shadow = '' 102 | return 103 | } 104 | if (/^B[1234]$/.test(Mem.shadow)) { 105 | Mem.shadow = '' 106 | } 107 | }) 108 | } 109 | 110 | getMemoryByName (name: string): MemoryObj { 111 | const RetObj = this.Memory.find(Mem => Mem.varName === name) 112 | if (RetObj === undefined) { 113 | throw new Error(`optimizerVM: getMemoryByName: Variable '${name}' not declared.`) 114 | } 115 | return RetObj 116 | } 117 | 118 | revokeShadow (changedVar: string) : void { 119 | this.Memory.forEach((Mem) => { 120 | if (Mem.shadow === changedVar) { 121 | Mem.shadow = '' 122 | } 123 | }) 124 | } 125 | 126 | unknownAndRevoke (Var: MemoryObj) : void { 127 | Var.value = unknownValue 128 | Var.shadow = '' 129 | this.revokeShadow(Var.varName) 130 | } 131 | 132 | setAndRevoke (AssignedVar: MemoryObj, Variable: MemoryObj) : void { 133 | AssignedVar.value = Variable.value 134 | AssignedVar.shadow = Variable.varName 135 | this.revokeShadow(AssignedVar.varName) 136 | Variable.shadow = AssignedVar.varName 137 | } 138 | 139 | zeroAndRevoke (Var: MemoryObj) : void { 140 | if (Var.value !== 0n) { 141 | Var.value = 0n 142 | Var.shadow = '' 143 | this.revokeShadow(Var.varName) 144 | } 145 | } 146 | 147 | /** 148 | * Runs only one instruction (step into) 149 | * 150 | * @return string indicating error/status. Empty string on success. 151 | */ 152 | optimize (): string[] { 153 | let cpuExitCode: boolean|null 154 | 155 | this.asmCodeArr.forEach((code, line) => { 156 | cpuExitCode = CPU.cpu(this, line) 157 | if (cpuExitCode === null) { 158 | this.asmCodeArr[line] = 'DELETE' 159 | } 160 | }) 161 | return this.asmCodeArr.filter(code => code !== 'DELETE') 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/typings/contractTypes.ts: -------------------------------------------------------------------------------- 1 | import { GLOBAL_CONTEXT } from '../codeGenerator/codeGeneratorTypes' 2 | import { BUILTIN_TYPES, DECLARATION_TYPES, MEMORY_SLOT, SENTENCES, TOKEN, TYPE_DEFINITIONS } from './syntaxTypes' 3 | 4 | export type SC_CONFIG = { 5 | /** Hardcoded compiler version!!! */ 6 | compilerVersion: string, 7 | /** Number of auxiliary vars to be declared by compiler: #pragma maxAuxVars */ 8 | maxAuxVars: number, 9 | /** Number of auxiliary Constants to be declared by compiler: #pragma maxConstVars */ 10 | maxConstVars: number, 11 | /** Make final global optimization: #pragma optimizationLevel */ 12 | optimizationLevel: number, 13 | /** Try to reuse variable at left side of assigment: #pragma reuseAssignedVar */ 14 | reuseAssignedVar: boolean, 15 | /** Support for API Functions: #include APIFunctions */ 16 | APIFunctions: boolean, 17 | /** Support for API Functions with fixed numbers: #include fixedAPIFunctions */ 18 | fixedAPIFunctions: boolean, 19 | /** Program Name: #program name */ 20 | PName: string, 21 | /** Program description: #program description */ 22 | PDescription: string, 23 | /** Program activationAmount: #program activationAmount */ 24 | PActivationAmount: string, 25 | /** Program creator: Used only in SC-Simulator. Ignored in machine code output. */ 26 | PCreator: string, 27 | /** Program contract ID: Used only in SC-Simulator. Ignored in machine code output. */ 28 | PContract: string, 29 | /** User stack pages to be available: #program userStackPages */ 30 | PUserStackPages: number, 31 | /** Code stack pages to be available:: #program codeStackPages */ 32 | PCodeStackPages: number, 33 | /** Machine code hash id to be matched during compilation: #program codeHashId */ 34 | PCodeHashId: string, 35 | /** Adds a comment in generated assembly code with source code line number and content */ 36 | verboseAssembly: boolean, 37 | /** Adds a comment when new scope start/ends with the free registers, and warns when one register is locked */ 38 | verboseScope: boolean, 39 | } 40 | 41 | export type SC_MACRO = { 42 | /** pragma, program or include */ 43 | type: string 44 | /** Macro property, only one word */ 45 | property: string 46 | /** Macro value, allowed many words */ 47 | value: string 48 | /** Line follows the scheme 'line:column' or '0:0' if unknown */ 49 | line: string 50 | } 51 | 52 | export type SC_FUNCTION = { 53 | /** type of function declaration */ 54 | declaration: DECLARATION_TYPES 55 | /** Property to control function substitution */ 56 | isInline: boolean 57 | /** type definition if is a struct function */ 58 | typeDefinition?: string 59 | /** Function name */ 60 | name: string 61 | /** Temporary, holding function arguments tokens */ 62 | arguments?: TOKEN[] 63 | /** Variables of function arguments */ 64 | argsMemObj: MEMORY_SLOT[] 65 | /** Temporary, holding function block tokens */ 66 | code?: TOKEN[] 67 | /** Definitive sentences for function block. */ 68 | sentences: SENTENCES[] 69 | /** Line number of function declaration */ 70 | line?: string 71 | /** Assembly name for API Functions only */ 72 | asmName?: string 73 | /** Built-in type */ 74 | builtin?: BUILTIN_TYPES 75 | } 76 | 77 | export type SC_GLOBAL = { 78 | /** Definitions for Built-In functions */ 79 | BuiltInFunctions: SC_FUNCTION[] 80 | /** Definitions for API functions */ 81 | APIFunctions: SC_FUNCTION[] 82 | /** macros values */ 83 | macros: SC_MACRO[] 84 | /** Temporary, holding tokens objects */ 85 | code?: TOKEN[] 86 | /** Definitive structure for compilation */ 87 | sentences: SENTENCES[] 88 | } 89 | 90 | export type CONTRACT = { 91 | /** Source code splitted by lines */ 92 | sourceLines: string[], 93 | /** Global statements and information */ 94 | Global: SC_GLOBAL, 95 | /** Declared functions */ 96 | functions: SC_FUNCTION[], 97 | /** Variables, constants and labels in memory */ 98 | memory: MEMORY_SLOT[], 99 | /** Extended information for arrays and structs */ 100 | typesDefinitions: TYPE_DEFINITIONS[], 101 | /** Compiler configurations */ 102 | Config: SC_CONFIG, 103 | /** Variables and helper functions */ 104 | Context: GLOBAL_CONTEXT 105 | } 106 | 107 | export type MACHINE_OBJECT = { 108 | /** Warnings found */ 109 | Warnings: string 110 | /** Number of data pages (Memory size) */ 111 | DataPages: number 112 | /** Number of code stack pages (code stack size) */ 113 | CodeStackPages: number 114 | /** Number of user stack pages (user stack size) */ 115 | UserStackPages: number 116 | /** Number of machine instructions pages */ 117 | CodePages: number 118 | /** Calculated minimum fee for contract deployment */ 119 | MinimumFeeNQT: string 120 | /** Hex string with contract machine code */ 121 | ByteCode: string 122 | /** Hash ID for compiled machine code */ 123 | MachineCodeHashId: string 124 | /** Hex string with contract starting memory values */ 125 | ByteData: string 126 | /** Array with variables names ordered in memory */ 127 | Memory: string[] 128 | /** Array with labels and their addresses (not ordered) */ 129 | Labels: { 130 | label: string 131 | address: number 132 | }[] 133 | /** Program assembly source code */ 134 | AssemblyCode: string 135 | /** Program name */ 136 | PName: string 137 | /** Program description */ 138 | PDescription: string 139 | /** Program activation amount */ 140 | PActivationAmount: string 141 | } 142 | -------------------------------------------------------------------------------- /samples/PublicMethodsOnSmartC.md: -------------------------------------------------------------------------------- 1 | # Public methods 2 | SmartJ uses a simple concept to integrate Smart Contracts. It works by defining a public method that can be called by other classes and also imported in other projects. SmartC does not have this kind of java integration, but the same result can be achieved. 3 | 4 | To be clear, skeleton codes for SmartC and SmartJ are shown. The main loop for processing messages is hidden in SmartJ, and this part calls the protected methods. Also the switch statement for routing transactions to the public methods is prepared. This is reached using magic numbers to make relationship between the message and the method. 5 | 6 | ## Discovering magic numbers 7 | Every public method will have a magic number associated. This value calculated internally in SmartJ and it is a hash of function name and arguments. To use in SmartC, we need to calculate this number using the following recipe: 8 | 9 | * Create a string with the function name then add a 'V' 10 | * Change the arguments to 'J' 11 | * Calculate the sha256 hash of this string 12 | * Take the first 8 bytes 13 | * Swap endianess 14 | 15 | Example 1: GetSnacks(long bites) 16 | * GetSnacks(long bites)V 17 | * GetSnacks(J)V 18 | * 5ee1891b7c9473fc1dc725c77f409fb9732b3778fd5ae93aba5ec82ff57be1f9 19 | * 5ee1891b7c9473fc 20 | * fc73947c1b89e15e 21 | * Magic number for GetSnacks(long bites) is 0xfc73947c1b89e15e 22 | 23 | Example 2: GetDrinks(long type, long quantity) 24 | * GetDrinks(long type, long quantity)V 25 | * GetDrinks(JJ)V 26 | * 62f9ff69b152d62a172b7bf47c437891357163ac3de8c1c0314dde45765921d5 27 | * 62f9ff69b152d62a 28 | * 2ad652b169fff962 29 | * Magic number for GetDrinks(long type, long quantity) is 0x2ad652b169fff962 30 | 31 | To automate the process you can use CyberChef with the recipe https://gchq.github.io/CyberChef/#recipe=SHA2('256',64,160)Take_bytes(0,16,false)Swap_endianness('Hex',8,true)&input=R2V0RHJpbmtzKEpKKVY 32 | 33 | ## SmartC program skeleton 34 | 35 | ```c 36 | // global variables, will be available in all functions 37 | long myVar; 38 | 39 | // ****** This part of processing is hidden in SmartJ ****** 40 | 41 | // Set public functions magic numbers 42 | #define GET_SNACKS 0xfc73947c1b89e15e 43 | #define GET_DRINKS 0x2ad652b169fff962 44 | 45 | struct TXINFO { 46 | long txId, 47 | timestamp, 48 | sender, 49 | amount, 50 | message[4]; 51 | } currentTX; 52 | 53 | constructor(); 54 | 55 | void main(void) { 56 | blockStarted(); 57 | while ((currentTX.txId = getNextTx()) != 0) { 58 | currentTX.sender = getSender(currentTX.txId); 59 | currentTX.amount = getAmount(currentTX.txId); 60 | readMessage(currentTX.txId, 0, currentTX.message); 61 | 62 | switch (currentTX.message[0]) { 63 | case GET_SNACKS: 64 | GetSnacks(currentTX.message[1]); 65 | break; 66 | case GET_DRINKS: 67 | GetDrinks(currentTX.message[1], currentTX.message[2]); 68 | break; 69 | default: 70 | txReceived(); 71 | } 72 | } 73 | blockFinished(); 74 | } 75 | // ****** end of hidden part ****** 76 | 77 | // ****** These are public methods in SmartJ ****** 78 | void GetSnacks(long bites) { 79 | // Do your stuff 80 | myPrivateMethod(); 81 | } 82 | 83 | void GetDrinks(long type, long quantity) { 84 | // Do your stuff 85 | } 86 | 87 | // ****** These are private methods in SmartJ ****** 88 | void myPrivateMethod() { 89 | // Do your stuff 90 | } 91 | 92 | // ****** These are protected methods in SmartJ ****** 93 | void constructor(void) { 94 | // this function will be called only once on first activation. 95 | } 96 | 97 | void txReceived(void) { 98 | // Will handle any incoming message that is not direct to public methods 99 | } 100 | 101 | void blockStarted(void) { 102 | // Run when contract is activated by a transaction, but before 103 | // to get the currentTX details. currentTX will have details from last 104 | // transaction received in a a previous block. 105 | } 106 | 107 | void blockFinished(void) { 108 | // Run when all transactions were already processed. currentTX will 109 | // keep the values of last transaction processed. 110 | } 111 | ``` 112 | 113 | ## Skeleton on SmartJ 114 | 115 | ```java 116 | package bt.sample; 117 | 118 | import bt.*; 119 | import bt.compiler.CompilerVersion; 120 | import bt.compiler.TargetCompilerVersion; 121 | import bt.ui.EmulatorWindow; 122 | 123 | /** 124 | * Skeleton class 125 | */ 126 | @TargetCompilerVersion(CompilerVersion.v0_0_0) 127 | public class Skeleton extends Contract { 128 | 129 | /** 130 | * Variables on class scope (available in all methods) 131 | */ 132 | long myVar; 133 | 134 | /** 135 | * Constructor, when in blockchain the constructor is called when the first TX 136 | * reaches the contract. 137 | */ 138 | public Skeleton(){ 139 | 140 | } 141 | 142 | /** 143 | * Get the snacks 144 | * 145 | * @param bites 146 | */ 147 | public void GetSnacks(long bites) { 148 | // Do your stuff 149 | } 150 | 151 | /** 152 | * Get the drinks 153 | * 154 | * @param type 155 | * @param quantity 156 | */ 157 | public void GetDrinks(long type, long quantity) { 158 | // Do your stuff 159 | myPrivateMethod(); 160 | } 161 | 162 | private void myPrivateMethod() { 163 | // Do your stuff 164 | } 165 | 166 | /** 167 | * Iterates over every transaction received 168 | */ 169 | @Override 170 | public void txReceived(){ 171 | 172 | } 173 | 174 | @Override 175 | protected void blockStarted() { 176 | 177 | } 178 | 179 | @Override 180 | protected void blockFinished() { 181 | 182 | } 183 | 184 | public static void main(String[] args) { 185 | new EmulatorWindow(Skeleton.class); 186 | } 187 | } 188 | ``` 189 | -------------------------------------------------------------------------------- /docs/6-Deeper-into-SmartC.md: -------------------------------------------------------------------------------- 1 | [Back](./README.md) 2 | 3 | ### Deploying Green Contracts 4 | Green contracts are a feature on Signum cryptocurrency where it is possible to save some deployment fee by using the same machine code already deployed. In this case, the fee is reduced because the green contract will use the transaction to store only new machine data (byteData or the initial memory state) and set up the new contract. It is important that the machine code shall be the same, which means that the machine code hash ID from both the green copy and original contract must match. 5 | 6 | Some contracts do not need a new initial state, so no modifications are needed in the source code. For others, it is possible to adjust some variables and still use a green copy. All variables that are initially set using the const keyword can be changed, and the machine code hash ID will still be the same, allowing a green copy deployment. 7 | 8 | #### What is needed? 9 | * The source code of the original contract. 10 | * From a full deployment transaction, keep the fullHash info. 11 | * Run the SmartC version indicated by the source code. It's possible to try the latest version, but in some cases, the newer version can add some optimizations and change the machine code hash ID. All version available [here](https://deleterium.info/SmartC/). 12 | 13 | #### What is optional? 14 | * Adjust the values of some variables, keeping the same machine code hash ID. 15 | 16 | #### How to deploy? 17 | 1) In the "Smart Contract Deployment" window, delete the field 'Bytecode'. 18 | 2) In the "Smart Contract Deployment" window, fill the field 'ReferencedTransactionFullHash' with the fullHash from original deployment. 19 | 3) Change the field "Fee (NQT)" to the minimum needed for a contract deployment. Currently it is 10000000 NQT. 20 | 4) Try to deploy. If the fee is too low, the node will tell you the correct fee to use. 21 | 5) Use the correct fee and the green contract will be deployed. 22 | 23 | Check a video showing the process at https://www.youtube.com/watch?v=CLlsUpswyYI , but note it is used an older version of SmartC and also Signum-node. 24 | 25 | 26 | ### Operators precedence 27 | Following table presents operators precedence order that are [based on C](https://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B#Operator_precedence) but with some simplifications. When two or more symbols with same precedence are in an expression, the operations will be evaluated from left to right, with exception for unary operators, assignment and keyword. Example: `a=16/4/4` will be evaluated as `a=(16/4)/4`, just like in C. If in doubt, use parenthesis! 28 | 29 | | Order | Symbol | Description | Associativity | 30 | | --- | --- | --- | --- | 31 | | 0 | Variable, Constant, Functions, `[]` `()` `{}` `.` `->` | Variables, constants, functions, arrays, scope, statements group, members | Left-to-right | 32 | | 1 | `++` `--` | Set unary operators | Depends* | 33 | | 2 | `!` `~` `-` `+` `*` `&` `sizeof` | Unary operators and sizeof keyword | Right-to-left | 34 | | 3 | `*` `/` `%` | Multiplication, division, modulo | Left-to-right | 35 | | 4 | `+` `-` | Addition and subtraction | Left-to-right | 36 | | 5 | `<<` `>>` | Bitwise shift left and right | Left-to-right | 37 | | 6 | `<` `<=` `>` `>=` `==` `!=` | Comparisons |Left-to-right | 38 | | 7 | `&` `^` `\|` | Bitwise AND XOR OR | Left-to-right | 39 | | 8 | `&&` | Logical AND | Left-to-right | 40 | | 9 | `\|\|` | Logical OR | Left-to-right | 41 | | 10 | `=` `+=` `-=` `/=` `*=` `%=` `&=` `^=` `\|=` `<<=` `>>=` | Assignment operators| Right-to-left | 42 | | 11 | `,` | Delimiter, comma | Left-to-right | 43 | | 12 | `;` `keywords` | Terminator, semi, keywords other than sizeof | Right-to-left | 44 | 45 | * Post increment and post decrement operators are exceptions, being applied on the neighbour variable. 46 | 47 | 48 | ### Internal names 49 | 50 | Tokens are divided in groups and later on checked if their combinations are syntactic valid. Compiler can show these names during errors. 51 | |Token type | Example/Operators | Description| 52 | | --- | --- | --- | 53 | | Variable | `var1` | Variables names. In special cases could be a pointer representation. | 54 | | Function | `func1(args)` | Function names. Represents a value returned by functions execution. | 55 | | Constant | `23` `0xffa` `'Hi!'` | Number to be stored inside a long value (64 bit). Strings are converted to number. | 56 | | Operator | `/` `%` `<<` `>>` `\|` `^` | Tokens that are undoubtly binary operators and have no other interpretation. | 57 | | UnaryOperator | `!` `~` | Tokens that are undoubtly unary operators and have no other interpretation. | 58 | | SetUnaryOperator | `++` `--` | Special unary operations with same meaning in C - pre/post increment/decrement | 59 | | Assignment | `=` | Common assignment operation | 60 | | SetOperator | `+=` `-=` `/=` `*=` `%=` `&=` `^=` `\|=` `<<=` `>>=` | Special assignment operations | 61 | | Comparision | `==` `<=` `<` `>` `>=` `!=` `&&` `\|\|` | Logical comparisions operations | 62 | | CheckOperator | `+` `-` `*` `&` | Tokens that have two meanings and need to be checked agains previous tokens to know their behaviour. After parsed they are treated as UnaryOperator or Operator | 63 | | Arr | `[expr]` | Representation of an array index. Must have a variable before it. | 64 | | CodeCave | `(expr...)` | Surrounding expressions to indicate that they shall be evaluated before others operations. In special case could be a pointer representation, or part of other keywords as `if`, `for`, ... | 65 | | CodeDomain | `{expr...}` | Surrounding expressions to indicate that it is a group of expressions | 66 | | Delimiter | `,` | Use if you want to write two expressions on same statement | 67 | | Terminator | `;` | Indicating the end of one statement | 68 | | Macro | `#` | Preprocessor statement, ends at a newline character. | 69 | | Member | `.` `->` | Used to select a struct member. | 70 | 71 | ### Internal object structure 72 | Please refer to typescript source code for details. 73 | 74 | [Back](./README.md) 75 | -------------------------------------------------------------------------------- /docs/4-Functions-repository.md: -------------------------------------------------------------------------------- 1 | [Back](./README.md) 2 | 3 | # Functions repository 4 | These functions and macros can be added to projects and speed up development time! 5 |
6 | 7 | 8 | ## Macros 9 | 10 | 11 | Macro functions are elaborated substitutions done during compilation. Use for very simple functions: 12 | 13 | ```c 14 | 15 | #define add2(val) (val + 2) 16 | 17 | #define sendSigna(recipient, amount) (\ 18 | Set_B1_B2(recipient, 0), \ 19 | F_Send_To_Address_In_B(amount)) 20 | 21 | #define sendMessage1(recipient, m1) (\ 22 | Clear_A_And_B(), \ 23 | Set_B1(recipient), \ 24 | Set_A1(m1), \ 25 | Send_A_To_Address_In_B()) 26 | 27 | // You got it! 28 | ``` 29 |
30 |
31 | 32 | 33 | ## Text to long: atol() 34 | 35 | 36 | ```c 37 | // ASCII to Long (base10 positive and less than 100.000.000) 38 | // Iterative function to implement atoi() clone function in C 39 | // Expects a long containing a string. If any byte is not a char numeric 40 | // representation, then stop and return. Only positive numbers, base10, 41 | // and integers are converted. Returns zero if no number was processed. 42 | const long n8 = 8, n10 = 10, n255 = 0xff; 43 | long atol(long val) 44 | { 45 | long ret = 0, chr; 46 | do { 47 | chr = (0xff & val) - '0'; 48 | if (chr < 0 || chr >= 10) 49 | break; 50 | ret *= 10; 51 | ret += chr; 52 | val >>= 8; 53 | } while (true); 54 | return ret; 55 | } 56 | ``` 57 |
58 |
59 | 60 | 61 | ## Long to text: ltoa() 62 | 63 | 64 | ```c 65 | // Integer to ASCII (base10 positive and less than 100.000.000) 66 | // Iterative function to implement itoa() clone function in C 67 | // Expects a long. If number is negative or bigger than MAX_LTOA 68 | // (it will not fit in a long), returns long meaning "#error". 69 | const long n8 = 8, n10 = 10; 70 | #define MAX_LTOA 99999999 71 | long ltoa(long val) 72 | { 73 | long ret; 74 | if (val < 0 || val > MAX_LTOA) 75 | return "#error"; 76 | ret = 0; 77 | do { 78 | ret <<= 8; 79 | ret += '0' + val % 10; 80 | val /= 10; 81 | } while (val != 0); 82 | return ret; 83 | } 84 | ``` 85 |
86 |
87 | 88 | 89 | ## Text to fixed: decodeAmount() 90 | 91 | 92 | ```c 93 | // ASCII to fixed (base10 positive) 94 | // Expects a string in currentTX.message. If any byte is not a char numeric 95 | // representation or decimal point, then stop and return. Only positive 96 | // numbers, base10 are converted. Returns zero if no number was processed. 97 | fixed decodeAmountInMessage(long startingAt) { 98 | long multiplier = 1_0000_0000; 99 | long retVal = 0; 100 | long ch; 101 | long decimals = false; 102 | for (long i = long startingAt; i < 32; i++) { 103 | ch = currentTX.message[i / 8] >> ((i % 8) * 8); 104 | ch &= 0xff; 105 | if (ch == 0 || ch == ' ') break; 106 | if (ch == '.') { 107 | decimals = true; 108 | continue; 109 | } 110 | if (ch < '0' || ch > '9' ) { 111 | // invalid char 112 | retVal = 0; 113 | break; 114 | } 115 | if (decimals) { 116 | multiplier /= 10; 117 | } else { 118 | retVal *= 10; 119 | } 120 | ch &= 0xF; 121 | ch *= multiplier; 122 | retVal += ch; 123 | } 124 | return bcltof(retVal); 125 | } 126 | ``` 127 |
128 |
129 | 130 | 131 | ## RS-Address to accounId: decodeRS() 132 | 133 | 134 | ```c 135 | // RS-Address to accountId 136 | // Expects a string containing an address in currentTX.message 137 | // starting at index zero. 138 | // Ex: S-AAAA-BBBB-CCCC-DDDDD. 139 | // On error returns -1. 140 | // Actually the first group can be any text, so matches 'S', 'TS' 141 | // or 'BURST'. 142 | // No error checks!! 143 | 144 | /** Decode an RS address at currentTX.message 145 | * returns -1 if there is an error on decoding. 146 | * This function does not verify error correction bytes 147 | * */ 148 | long decodeRS(void) { 149 | long position = 0, ch, group = 0; 150 | long idPosition, value, result = 0; 151 | for (i = 0; i < 32; i++) { 152 | ch = currentTX.message[i / 8] >> ((i % 8) * 8); 153 | ch &= 0xff; 154 | if (ch == '-') { 155 | group++; 156 | continue; 157 | } 158 | if (group == 0 || group == 3) { 159 | continue; 160 | } 161 | value = rscharToIndex(ch); 162 | if (value == minus1) { 163 | // ERROR 164 | return minus1; 165 | } 166 | idPosition = (codeword_map >> (position * 4)) & 15; 167 | result |= value << (idPosition * 5); 168 | position++; 169 | if (position == 13) { 170 | return result; 171 | } 172 | } 173 | return minus1; 174 | } 175 | 176 | const long codeword_map = 177 | 3 + 178 | 2 *16 + 179 | 1 *16*16 + 180 | 0 *16*16*16 + 181 | 7 *16*16*16*16 + 182 | 6 *16*16*16*16*16 + 183 | 5 *16*16*16*16*16*16 + 184 | 4 *16*16*16*16*16*16*16 + 185 | 12 *16*16*16*16*16*16*16*16 + 186 | 8 *16*16*16*16*16*16*16*16*16 + 187 | 9 *16*16*16*16*16*16*16*16*16*16 + 188 | 10 *16*16*16*16*16*16*16*16*16*16*16 + 189 | 11 *16*16*16*16*16*16*16*16*16*16*16*16; 190 | 191 | long rscharToIndex(long in) { 192 | switch (true) { 193 | case (in < '2'): 194 | case (in == 'O'): 195 | case (in == 'I'): 196 | return minus1; 197 | case (in <= '9'): 198 | return in - '2'; 199 | case (in < 'A'): 200 | return minus1; 201 | case (in < 'I'): 202 | return in - '9'; 203 | case (in < 'O'): 204 | return in - ':'; 205 | case (in <= 'Z'): 206 | return in - ';'; 207 | default: 208 | return minus1; 209 | } 210 | } 211 | ``` 212 |
213 | 214 | [Back](./README.md) 215 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es2020", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ 8 | //"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ 13 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | //"sourceMap": true, /* Generates corresponding '.map' file. */ 16 | //"outFile": "", /* Concatenate and emit output to single file. */ 17 | "outDir": "./dist", /* Redirect output structure to the directory. */ 18 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | "strictNullChecks": true, /* Enable strict null checks. */ 31 | "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 43 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ 44 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ 45 | 46 | /* Module Resolution Options */ 47 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 48 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 49 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 50 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 51 | // "typeRoots": [], /* List of folders to include type definitions from. */ 52 | // "types": [], /* Type declaration files to be included in compilation. */ 53 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 54 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 55 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 56 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 57 | 58 | /* Source Map Options */ 59 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 60 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 61 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 62 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 63 | 64 | /* Experimental Options */ 65 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 66 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 67 | 68 | /* Advanced Options */ 69 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 70 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 71 | }, 72 | "exclude": [ 73 | "**/*.spec.ts", 74 | "dist/**/*" 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /src/assembler/hashMachineCode.ts: -------------------------------------------------------------------------------- 1 | /* LICENSE notes for function binb_sha256: 2 | * 3 | * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message 4 | * Digest Algorithm, as defined in RFC 1321. 5 | * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009 6 | * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet 7 | * Distributed under the BSD License 8 | * See http://pajhome.org.uk/crypt/md5 for more info. 9 | */ 10 | 11 | /** 12 | * Calculates a digest ID of a given machine code. 13 | * @param hexCode Machine code to be calculated, as hex string. 14 | * @returns A string representing a hash ID of given contract 15 | */ 16 | export default function hashMachineCode (hexCode: string): string { 17 | // Pad input to match codepage length 18 | const hexLen = Number(((BigInt(hexCode.length) / 512n) + 1n) * 512n) 19 | hexCode = hexCode.padEnd(hexLen, '0') 20 | // Split program into words (32-bit) (matches always) 21 | const parts = hexCode.match(/[\s\S]{8}/g) as RegExpMatchArray 22 | // Translate hex text to words. 23 | const codeWordArr = parts.map(str => unsigned2signed(Number('0x' + str))) 24 | // Calculate sha-256 and toggle result to little endian 25 | const wordsHash = toggleEndianWordsArr(binb_sha256(codeWordArr, 4 * 8 * codeWordArr.length)) 26 | // Get parts for digest ID 27 | const lsp = BigInt(signed2unsigned(wordsHash[0])) 28 | const msp = BigInt(signed2unsigned(wordsHash[1])) 29 | // Calculate ID value and return it as string. 30 | return ((msp << 32n) + lsp).toString(10) 31 | 32 | function toggleEndianWordsArr (wordArr: number[]) { 33 | const bi = wordArr.map(x => signed2unsigned(x)) 34 | const retArr: number[] = [] 35 | let val: number 36 | for (const currBi of bi) { 37 | val = (currBi >> 24) & 0xff 38 | val |= ((currBi >> 16) & 0xff) << 8 39 | val |= ((currBi >> 8) & 0xff) << 16 40 | val |= (currBi & 0xff) << 24 41 | retArr.push(unsigned2signed(val)) 42 | } 43 | return retArr 44 | } 45 | 46 | // For 32-bit Number 47 | function unsigned2signed (unsigned: number) { 48 | if (unsigned >= 0x80000000) { 49 | return unsigned - 0x100000000 50 | } 51 | return unsigned 52 | } 53 | 54 | // For 32-bit Number 55 | function signed2unsigned (signed: number) { 56 | if (signed < 0) { 57 | return (signed + 0x100000000) 58 | } 59 | return signed 60 | } 61 | 62 | /* eslint-disable camelcase */ 63 | /* Calculate the SHA-256 of an array of big-endian words, and a bit length. */ 64 | function binb_sha256 (m: number[], l: number) { 65 | function safe_add (x: number, y: number) { 66 | const lsw = (x & 0xFFFF) + (y & 0xFFFF) 67 | const msw = (x >> 16) + (y >> 16) + (lsw >> 16) 68 | return (msw << 16) | (lsw & 0xFFFF) 69 | } 70 | /* sha256 support functions */ 71 | function sha256_S (X: number, n: number) { 72 | return (X >>> n) | (X << (32 - n)) 73 | } 74 | function sha256_R (X: number, n: number) { 75 | return (X >>> n) 76 | } 77 | function sha256_Ch (x: number, y: number, z: number) { 78 | return ((x & y) ^ ((~x) & z)) 79 | } 80 | function sha256_Maj (x: number, y: number, z: number) { 81 | return ((x & y) ^ (x & z) ^ (y & z)) 82 | } 83 | function sha256_Sigma0256 (x: number) { 84 | return (sha256_S(x, 2) ^ sha256_S(x, 13) ^ sha256_S(x, 22)) 85 | } 86 | function sha256_Sigma1256 (x: number) { 87 | return (sha256_S(x, 6) ^ sha256_S(x, 11) ^ sha256_S(x, 25)) 88 | } 89 | function sha256_Gamma0256 (x: number) { 90 | return (sha256_S(x, 7) ^ sha256_S(x, 18) ^ sha256_R(x, 3)) 91 | } 92 | function sha256_Gamma1256 (x: number) { 93 | return (sha256_S(x, 17) ^ sha256_S(x, 19) ^ sha256_R(x, 10)) 94 | } 95 | /* Main sha256 function */ 96 | const sha256_K = [ 97 | 1116352408, 1899447441, -1245643825, -373957723, 961987163, 1508970993, 98 | -1841331548, -1424204075, -670586216, 310598401, 607225278, 1426881987, 99 | 1925078388, -2132889090, -1680079193, -1046744716, -459576895, -272742522, 100 | 264347078, 604807628, 770255983, 1249150122, 1555081692, 1996064986, 101 | -1740746414, -1473132947, -1341970488, -1084653625, -958395405, -710438585, 102 | 113926993, 338241895, 666307205, 773529912, 1294757372, 1396182291, 103 | 1695183700, 1986661051, -2117940946, -1838011259, -1564481375, -1474664885, 104 | -1035236496, -949202525, -778901479, -694614492, -200395387, 275423344, 105 | 430227734, 506948616, 659060556, 883997877, 958139571, 1322822218, 106 | 1537002063, 1747873779, 1955562222, 2024104815, -2067236844, -1933114872, 107 | -1866530822, -1538233109, -1090935817, -965641998 108 | ] 109 | 110 | const HASH = [1779033703, -1150833019, 1013904242, -1521486534, 111 | 1359893119, -1694144372, 528734635, 1541459225] 112 | const W = new Array(64) 113 | let a, b, c, d, e, f, g, h 114 | let i, j, T1, T2 115 | 116 | /* append padding */ 117 | m[l >> 5] |= 0x80 << (24 - l % 32) 118 | m[((l + 64 >> 9) << 4) + 15] = l 119 | 120 | for (i = 0; i < m.length; i += 16) { 121 | a = HASH[0] 122 | b = HASH[1] 123 | c = HASH[2] 124 | d = HASH[3] 125 | e = HASH[4] 126 | f = HASH[5] 127 | g = HASH[6] 128 | h = HASH[7] 129 | 130 | for (j = 0; j < 64; j++) { 131 | if (j < 16) { 132 | W[j] = m[j + i] 133 | } else { 134 | W[j] = safe_add(safe_add(safe_add(sha256_Gamma1256(W[j - 2]), W[j - 7]), 135 | sha256_Gamma0256(W[j - 15])), W[j - 16]) 136 | } 137 | 138 | T1 = safe_add(safe_add(safe_add(safe_add(h, sha256_Sigma1256(e)), sha256_Ch(e, f, g)), 139 | sha256_K[j]), W[j]) 140 | T2 = safe_add(sha256_Sigma0256(a), sha256_Maj(a, b, c)) 141 | h = g 142 | g = f 143 | f = e 144 | e = safe_add(d, T1) 145 | d = c 146 | c = b 147 | b = a 148 | a = safe_add(T1, T2) 149 | } 150 | 151 | HASH[0] = safe_add(a, HASH[0]) 152 | HASH[1] = safe_add(b, HASH[1]) 153 | HASH[2] = safe_add(c, HASH[2]) 154 | HASH[3] = safe_add(d, HASH[3]) 155 | HASH[4] = safe_add(e, HASH[4]) 156 | HASH[5] = safe_add(f, HASH[5]) 157 | HASH[6] = safe_add(g, HASH[6]) 158 | HASH[7] = safe_add(h, HASH[7]) 159 | } 160 | return HASH 161 | } 162 | /* eslint-enable camelcase */ 163 | } 164 | -------------------------------------------------------------------------------- /src/codeGenerator/__tests__/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import utils from '../utils' 2 | 3 | describe('codeGenerator utils functions (long)', () => { 4 | it('should calculate: addConstants long, long', () => { 5 | expect(utils.addConstants( 6 | { value: '000a000', declaration: 'long' }, 7 | { value: 'aaa0aaa', declaration: 'long' } 8 | )).toStrictEqual({ value: 0xaaaaaaan, declaration: 'long' }) 9 | expect(utils.addConstants( 10 | { value: 139840123, declaration: 'long' }, 11 | { value: 93364483, declaration: 'long' } 12 | )).toStrictEqual({ value: 0xde66b7en, declaration: 'long' }) 13 | expect(utils.addConstants( 14 | { value: 'fffffffffffffff6', declaration: 'long' }, 15 | { value: 20, declaration: 'long' } 16 | )).toStrictEqual({ value: 0xan, declaration: 'long' }) 17 | }) 18 | it('should calculate: subConstants long, long', () => { 19 | expect(utils.subConstants( 20 | { value: '0000', declaration: 'long' }, 21 | { value: 'a', declaration: 'long' } 22 | )).toStrictEqual({ value: 0xfffffffffffffff6n, declaration: 'long' }) 23 | expect(utils.subConstants( 24 | { value: 20, declaration: 'long' }, 25 | { value: 10, declaration: 'long' } 26 | )).toStrictEqual({ value: 0xan, declaration: 'long' }) 27 | }) 28 | it('should calculate: mulHexContents long, long', () => { 29 | expect(utils.mulConstants( 30 | { value: '0000', declaration: 'long' }, 31 | { value: 'a', declaration: 'long' } 32 | )).toStrictEqual({ value: 0n, declaration: 'long' }) 33 | expect(utils.mulConstants( 34 | { value: 20, declaration: 'long' }, 35 | { value: 10, declaration: 'long' } 36 | )).toStrictEqual({ value: 0xc8n, declaration: 'long' }) 37 | expect(utils.mulConstants( 38 | { value: '89528e28c0542dba', declaration: 'long' }, 39 | { value: 4, declaration: 'long' } 40 | )).toStrictEqual({ value: 0x254a38a30150b6e8n, declaration: 'long' }) 41 | }) 42 | it('should calculate: divHexContents long, long', () => { 43 | expect(utils.divConstants( 44 | { value: 11, declaration: 'long' }, 45 | { value: 2, declaration: 'long' } 46 | )).toStrictEqual({ value: 0x5n, declaration: 'long' }) 47 | expect(utils.divConstants( 48 | { value: 0, declaration: 'long' }, 49 | { value: 10, declaration: 'long' } 50 | )).toStrictEqual({ value: 0n, declaration: 'long' }) 51 | }) 52 | test('should throw: divHexContents long, long', () => { 53 | expect(() => { 54 | utils.divConstants( 55 | { value: 11, declaration: 'long' }, 56 | { value: '', declaration: 'long' } 57 | ) 58 | }).toThrowError('Division by zero') 59 | }) 60 | it('should create: createConstantMemObj Number', () => { 61 | const MemObj = utils.createConstantMemObj(32) 62 | expect(MemObj.hexContent).toBe('0000000000000020') 63 | expect(MemObj.declaration).toBe('long') 64 | expect(MemObj.size).toBe(1) 65 | }) 66 | it('should create: createConstantMemObj Bigint', () => { 67 | const MemObj = utils.createConstantMemObj(320n) 68 | expect(MemObj.hexContent).toBe('0000000000000140') 69 | expect(MemObj.declaration).toBe('long') 70 | }) 71 | it('should create: createConstantMemObj Bigint overflow', () => { 72 | const MemObj = utils.createConstantMemObj(18446744073709551616n) 73 | expect(MemObj.hexContent).toBe('00000000000000010000000000000000') 74 | expect(MemObj.declaration).toBe('long') 75 | expect(MemObj.size).toBe(2) 76 | }) 77 | test('should throw: createConstantMemObj Fixed', () => { 78 | expect(() => { 79 | utils.createConstantMemObj(1.5) 80 | }).toThrowError('Internal error') 81 | }) 82 | it('should create: createConstantMemObj String', () => { 83 | const MemObj = utils.createConstantMemObj('feff') 84 | expect(MemObj.hexContent).toBe('000000000000feff') 85 | expect(MemObj.declaration).toBe('long') 86 | }) 87 | }) 88 | 89 | describe('codeGenerator utils functions (mixed long/fixed)', () => { 90 | it('should calculate: addConstants (mixed)', () => { 91 | expect(utils.addConstants( 92 | { value: '000a000', declaration: 'long' }, 93 | { value: 'aaa0aaa', declaration: 'fixed' } 94 | )).toStrictEqual({ value: 0x3B9B74A0AAAn, declaration: 'fixed' }) 95 | expect(utils.addConstants( 96 | { value: 139840123, declaration: 'fixed' }, 97 | { value: 93364483, declaration: 'fixed' } 98 | )).toStrictEqual({ value: 0xde66b7en, declaration: 'fixed' }) 99 | expect(utils.addConstants( 100 | { value: 0x3b9aca01, declaration: 'fixed' }, 101 | { value: 'fffffffffffffff6', declaration: 'long' } 102 | )).toStrictEqual({ value: 0x1n, declaration: 'fixed' }) 103 | }) 104 | it('should calculate: subConstants (mixed)', () => { 105 | expect(utils.subConstants( 106 | { value: '0000', declaration: 'long' }, 107 | { value: 'a', declaration: 'fixed' } 108 | )).toStrictEqual({ value: 0xfffffffffffffff6n, declaration: 'fixed' }) 109 | expect(utils.subConstants( 110 | { value: 200000002, declaration: 'fixed' }, 111 | { value: 1, declaration: 'long' } 112 | )).toStrictEqual({ value: 100000002n, declaration: 'fixed' }) 113 | expect(utils.subConstants( 114 | { value: 300000003, declaration: 'fixed' }, 115 | { value: 200000002, declaration: 'fixed' } 116 | )).toStrictEqual({ value: 100000001n, declaration: 'fixed' }) 117 | }) 118 | it('should calculate: mulHexContents (mixed)', () => { 119 | expect(utils.mulConstants( 120 | { value: '0000', declaration: 'fixed' }, 121 | { value: 'a', declaration: 'long' } 122 | )).toStrictEqual({ value: 0n, declaration: 'fixed' }) 123 | expect(utils.mulConstants( 124 | { value: 20, declaration: 'long' }, 125 | { value: 10, declaration: 'fixed' } 126 | )).toStrictEqual({ value: 0xc8n, declaration: 'fixed' }) 127 | expect(utils.mulConstants( 128 | { value: 300000003, declaration: 'fixed' }, 129 | { value: 200000002, declaration: 'fixed' } 130 | )).toStrictEqual({ value: 600000012n, declaration: 'fixed' }) 131 | }) 132 | it('should calculate: divHexContents (mixed)', () => { 133 | expect(utils.divConstants( 134 | { value: 11, declaration: 'fixed' }, 135 | { value: 2, declaration: 'long' } 136 | )).toStrictEqual({ value: 0x5n, declaration: 'fixed' }) 137 | expect(utils.divConstants( 138 | { value: 0, declaration: 'long' }, 139 | { value: 10, declaration: 'fixed' } 140 | )).toStrictEqual({ value: 0n, declaration: 'fixed' }) 141 | expect(utils.divConstants( 142 | { value: 1, declaration: 'long' }, 143 | { value: 10000000, declaration: 'fixed' } 144 | )).toStrictEqual({ value: 1000000000n, declaration: 'fixed' }) 145 | expect(utils.divConstants( 146 | { value: 200000002, declaration: 'fixed' }, 147 | { value: 300030000, declaration: 'fixed' } 148 | )).toStrictEqual({ value: 66660001n, declaration: 'fixed' }) 149 | }) 150 | }) 151 | -------------------------------------------------------------------------------- /src/__tests__/pointers.a.spec.ts: -------------------------------------------------------------------------------- 1 | import { SmartC } from '../smartc' 2 | 3 | describe('Pointer assignment', () => { 4 | it('should compile: pointer regular use', () => { 5 | const code = 'long *pa, *pb, va, vb;\npa = pb; pa = &pb; pa = &vb; *pa= vb;\n *pa= *pb; va = vb; va = *pb;' 6 | const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare pa\n^declare pb\n^declare va\n^declare vb\n\nSET @pa $pb\nSET @pa #0000000000000004\nSET @pa #0000000000000006\nSET @($pa) $vb\nSET @r0 $($pb)\nSET @($pa) $r0\nSET @va $vb\nSET @va $($pb)\nFIN\n' 7 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 8 | compiler.compile() 9 | expect(compiler.getAssemblyCode()).toBe(assembly) 10 | }) 11 | test('should throw: pointer wrong types', () => { 12 | expect(() => { 13 | const code = 'long *pa, *pb, va, vb; pa=vb; pa=*pb; *pa=pb; *pa=&pb; *pa=&vb; va=pb; va=&pb; va=&vb;' 14 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 15 | compiler.compile() 16 | }).toThrowError(/^At line/) 17 | }) 18 | it('should compile: pointer + setOperator', () => { 19 | const code = 'long *pa, *pb, va, vb; pa+=vb;' 20 | const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare pa\n^declare pb\n^declare va\n^declare vb\n\nADD @pa $vb\nFIN\n' 21 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 22 | compiler.compile() 23 | expect(compiler.getAssemblyCode()).toBe(assembly) 24 | }) 25 | it('should compile: Pointer + SetOperator + operator', () => { 26 | const code = 'long *pa, *pb, va, vb; pa+=vb+1;' 27 | const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare pa\n^declare pb\n^declare va\n^declare vb\n\nSET @r0 $vb\nINC @r0\nADD @pa $r0\nFIN\n' 28 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 29 | compiler.compile() 30 | expect(compiler.getAssemblyCode()).toBe(assembly) 31 | }) 32 | it('should compile: pointer + setOperator', () => { 33 | const code = 'long *pa, *pb, va, vb; pa-=vb;' 34 | const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare pa\n^declare pb\n^declare va\n^declare vb\n\nSUB @pa $vb\nFIN\n' 35 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 36 | compiler.compile() 37 | expect(compiler.getAssemblyCode()).toBe(assembly) 38 | }) 39 | it('should compile: operator with pointer', () => { 40 | const code = 'long *pa, *pb, va, vb; pa=pa-vb;' 41 | const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare pa\n^declare pb\n^declare va\n^declare vb\n\nSET @r0 $pa\nSUB @r0 $vb\nSET @pa $r0\nFIN\n' 42 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 43 | compiler.compile() 44 | expect(compiler.getAssemblyCode()).toBe(assembly) 45 | }) 46 | test('should throw: pointer of pointer', () => { 47 | expect(() => { 48 | const code = 'long *pa, *pb, va, vb; va=*vb;' 49 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 50 | compiler.compile() 51 | }).toThrowError(/^At line/) 52 | }) 53 | test('should throw: wrong type (assignment)', () => { 54 | expect(() => { 55 | const code = 'long *pa, *pb, va, vb; *va=vb;' 56 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 57 | compiler.compile() 58 | }).toThrowError(/^At line/) 59 | }) 60 | test('should throw: invalid left side (operation on deferenced pointer)', () => { 61 | expect(() => { 62 | const code = 'long *a, *a+1=0;' 63 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 64 | compiler.compile() 65 | }).toThrowError(/^At line/) 66 | }) 67 | test('should throw: Invalid operator for pointer', () => { 68 | expect(() => { 69 | const code = 'long *a, *(a*3)=0;' 70 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 71 | compiler.compile() 72 | }).toThrowError(/^At line/) 73 | }) 74 | }) 75 | 76 | describe('Pointer/Array assignment', () => { 77 | it('should compile: regular use', () => { 78 | const code = 'long a[4], *b, c; *b=a[0]; a[0]=*b; b=a; *b=a[c]; a[c]=*b;' 79 | const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare a\n^const SET @a #0000000000000004\n^declare a_0\n^declare a_1\n^declare a_2\n^declare a_3\n^declare b\n^declare c\n\nSET @($b) $a_0\nSET @a_0 $($b)\nSET @b $a\nSET @r0 $($a + $c)\nSET @($b) $r0\nSET @r0 $($b)\nSET @($a + $c) $r0\nFIN\n' 80 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 81 | compiler.compile() 82 | expect(compiler.getAssemblyCode()).toBe(assembly) 83 | }) 84 | test('should throw: can not reassing an array', () => { 85 | expect(() => { 86 | const code = 'long a[4], *b, c; a=b;' 87 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 88 | compiler.compile() 89 | }).toThrowError(/^At line/) 90 | }) 91 | it('should compile: Address of array to pointer', () => { 92 | const code = 'long a[4], *b, c; b=&a; b=&a[0]; b=&c;' 93 | const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare a\n^const SET @a #0000000000000004\n^declare a_0\n^declare a_1\n^declare a_2\n^declare a_3\n^declare b\n^declare c\n\nSET @b #0000000000000003\nSET @b #0000000000000004\nSET @b #0000000000000009\nFIN\n' 94 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 95 | compiler.compile() 96 | expect(compiler.getAssemblyCode()).toBe(assembly) 97 | }) 98 | test('should throw: Address of array to regular variable', () => { 99 | expect(() => { 100 | const code = 'long a[4], *b, c; c=&a; c=&a[0]; c=&c;' 101 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 102 | compiler.compile() 103 | }).toThrowError(/^At line/) 104 | }) 105 | it('should compile: Operation with pointer before deferencing', () => { 106 | const code = 'long *a, b, c;\n*(a+1)=b; *(a+30)=b; *(a+c)=b;\nb=*(a+1); b=*(a+30); b=*(a+c);' 107 | const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare a\n^declare b\n^declare c\n\nSET @r0 $a\nINC @r0\nSET @($r0) $b\nSET @r0 #000000000000001e\nADD @r0 $a\nSET @($r0) $b\nSET @r0 $a\nADD @r0 $c\nSET @($r0) $b\nSET @b $a\nINC @b\nSET @b $($b)\nSET @b #000000000000001e\nADD @b $a\nSET @b $($b)\nSET @b $a\nADD @b $c\nSET @b $($b)\nFIN\n' 108 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 109 | compiler.compile() 110 | expect(compiler.getAssemblyCode()).toBe(assembly) 111 | }) 112 | it('should compile: Operations with array variable', () => { 113 | const code = 'long *a, b, Array[4]; a=Array+2; a=Array-b; a+=7; a++;' 114 | const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare a\n^declare b\n^declare Array\n^const SET @Array #0000000000000006\n^declare Array_0\n^declare Array_1\n^declare Array_2\n^declare Array_3\n\nSET @a $Array\nINC @a\nINC @a\nSET @a $Array\nSUB @a $b\nSET @r0 #0000000000000007\nADD @a $r0\nINC @a\nFIN\n' 115 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 116 | compiler.compile() 117 | expect(compiler.getAssemblyCode()).toBe(assembly) 118 | }) 119 | test('should throw: Forbidden operations with array variables', () => { 120 | expect(() => { 121 | const code = 'long *a, b, Array[4]; a=Array*2;' 122 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 123 | compiler.compile() 124 | }).toThrowError(/^At line/) 125 | }) 126 | }) 127 | -------------------------------------------------------------------------------- /src/codeGenerator/assemblyProcessor/operatorToAsm.ts: -------------------------------------------------------------------------------- 1 | import { assertExpression } from '../../repository/repository' 2 | import { CONTRACT } from '../../typings/contractTypes' 3 | import { TOKEN, MEMORY_SLOT } from '../../typings/syntaxTypes' 4 | import utils from '../utils' 5 | import { createInstruction, flattenMemory } from './createInstruction' 6 | 7 | /** 8 | * Create assembly intructions for binary operators or SetOperators. 9 | * @returns the assembly code necessary for the assignment to happen 10 | */ 11 | export default function operatorToAsm ( 12 | Program: CONTRACT, OperatorToken: TOKEN, LeftMem: MEMORY_SLOT, RightMem: MEMORY_SLOT 13 | ) : string { 14 | const FlatLeft = flattenMemory(Program, LeftMem, OperatorToken.line) 15 | const FlatRight = flattenMemory(Program, RightMem, OperatorToken.line) 16 | 17 | function operatorToAsmMain () : string { 18 | assertExpression(LeftMem.type !== 'constant') 19 | 20 | if (FlatLeft.FlatMem.declaration === 'fixed') { 21 | return leftFixedToAsm() 22 | } 23 | if (FlatRight.FlatMem.declaration === 'fixed') { 24 | throw new Error('Internal error') 25 | } 26 | return leftRegularRightRegularToAsm() 27 | } 28 | 29 | function leftFixedToAsm () : string { 30 | if (FlatRight.FlatMem.declaration === 'fixed') { 31 | return leftFixedRightFixedToAsm() 32 | } 33 | return leftFixedRightRegularToAsm() 34 | } 35 | 36 | function leftFixedRightFixedToAsm () : string { 37 | switch (OperatorToken.value) { 38 | case '+': 39 | case '+=': 40 | case '-': 41 | case '-=': 42 | return returnThisCode(chooseOperator(OperatorToken.value) + 43 | ` @${FlatLeft.FlatMem.asmName} $${FlatRight.FlatMem.asmName}\n`) 44 | case '*': 45 | case '*=': 46 | return returnThisCode(`MDV @${FlatLeft.FlatMem.asmName} $${FlatRight.FlatMem.asmName} $f100000000\n`) 47 | case '/': 48 | case '/=': 49 | return returnThisCode(`MDV @${FlatLeft.FlatMem.asmName} $f100000000 $${FlatRight.FlatMem.asmName}\n`) 50 | default: 51 | // % & | ^ << >> 52 | throw new Error('Internal error') 53 | } 54 | } 55 | 56 | function leftFixedRightRegularToAsm () : string { 57 | switch (OperatorToken.value) { 58 | case '/': 59 | case '/=': 60 | case '*': 61 | case '*=': 62 | case '>>': 63 | case '>>=': 64 | case '<<': 65 | case '<<=': 66 | return returnThisCode(chooseOperator(OperatorToken.value) + 67 | ` @${FlatLeft.FlatMem.asmName} $${FlatRight.FlatMem.asmName}\n`) 68 | default: 69 | // + - % & | ^ 70 | throw new Error('Internal error') 71 | } 72 | } 73 | 74 | function leftRegularRightRegularToAsm () : string { 75 | const optimized = tryOptimization() 76 | if (optimized !== undefined) { 77 | return optimized 78 | } 79 | return returnThisCode(chooseOperator(OperatorToken.value) + 80 | ` @${FlatLeft.FlatMem.asmName} $${FlatRight.FlatMem.asmName}\n`) 81 | } 82 | 83 | function tryOptimization () : string | undefined { 84 | if (RightMem.type === 'constant') { 85 | const optimizationResult = testOptimizations() 86 | if (optimizationResult === undefined) { 87 | Program.Context.freeRegister(FlatRight.FlatMem.address) 88 | return '' 89 | } 90 | if (optimizationResult.length > 0) { 91 | // Optimizations before already flat left side. 92 | assertExpression(FlatLeft.isNew === false, 'Internal error. Expecting not new item.') 93 | return FlatLeft.asmCode + optimizationResult 94 | } 95 | } 96 | } 97 | /** Check and do optimization on a constant right side. 98 | * Returns undefined if optimization is do nothing 99 | * Returns empty string if no optimization was found 100 | * Returns assembly code with optimized code 101 | */ 102 | function testOptimizations () : string|undefined { 103 | // if add new condition here, add also in checkOperatorOptimization code oKSx4ab 104 | // here we can have optimizations for all operations. 105 | switch (OperatorToken.value) { 106 | case '+': 107 | case '+=': 108 | return testOptimizationsPlus() 109 | case '-': 110 | case '-=': 111 | return testOptimizationsMinus() 112 | case '*': 113 | case '*=': 114 | return testOptimizationsMultiply() 115 | case '/': 116 | case '/=': 117 | return testOptimizationsDivide() 118 | default: 119 | return '' 120 | } 121 | } 122 | 123 | function testOptimizationsPlus () : string|undefined { 124 | switch (RightMem.hexContent) { 125 | case '0000000000000000': 126 | return 127 | case '0000000000000001': 128 | Program.Context.freeRegister(FlatRight.FlatMem.address) 129 | return createInstruction(Program, utils.genIncToken(), FlatLeft.FlatMem) 130 | case '0000000000000002': 131 | Program.Context.freeRegister(FlatRight.FlatMem.address) 132 | if (!Program.memory.find(MEM => MEM.asmName === 'n2' && MEM.hexContent === '0000000000000002')) { 133 | return createInstruction(Program, utils.genIncToken(), FlatLeft.FlatMem) + 134 | createInstruction(Program, utils.genIncToken(), FlatLeft.FlatMem) 135 | } 136 | } 137 | return '' 138 | } 139 | 140 | function testOptimizationsMinus () : string|undefined { 141 | if (RightMem.hexContent === '0000000000000000') { 142 | return 143 | } 144 | if (RightMem.hexContent === '0000000000000001') { 145 | Program.Context.freeRegister(FlatRight.FlatMem.address) 146 | return createInstruction(Program, utils.genDecToken(), FlatLeft.FlatMem) 147 | } 148 | return '' 149 | } 150 | 151 | function testOptimizationsMultiply () : string|undefined { 152 | if (RightMem.hexContent === '0000000000000001') { 153 | Program.Context.freeRegister(FlatRight.FlatMem.address) 154 | return 155 | } 156 | if (RightMem.hexContent === '0000000000000000') { 157 | Program.Context.freeRegister(FlatRight.FlatMem.address) 158 | return createInstruction(Program, utils.genAssignmentToken(OperatorToken.line), FlatLeft.FlatMem, RightMem) 159 | } 160 | return '' 161 | } 162 | 163 | function testOptimizationsDivide () : string|undefined { 164 | if (RightMem.hexContent === '0000000000000001') { 165 | Program.Context.freeRegister(FlatRight.FlatMem.address) 166 | return 167 | } 168 | return '' 169 | } 170 | 171 | function returnThisCode (asm : string) : string { 172 | Program.Context.freeRegister(FlatRight.FlatMem.address) 173 | if (FlatLeft.isNew === true) { 174 | asm += createInstruction(Program, utils.genAssignmentToken(OperatorToken.line), LeftMem, FlatLeft.FlatMem) 175 | Program.Context.freeRegister(FlatLeft.FlatMem.address) 176 | } 177 | return FlatLeft.asmCode + FlatRight.asmCode + asm 178 | } 179 | 180 | return operatorToAsmMain() 181 | } 182 | 183 | function chooseOperator (value: string) : string { 184 | switch (value) { 185 | case '+': 186 | case '+=': 187 | return 'ADD' 188 | case '-': 189 | case '-=': 190 | return 'SUB' 191 | case '*': 192 | case '*=': 193 | return 'MUL' 194 | case '/': 195 | case '/=': 196 | return 'DIV' 197 | case '|': 198 | case '|=': 199 | return 'BOR' 200 | case '&': 201 | case '&=': 202 | return 'AND' 203 | case '^': 204 | case '^=': 205 | return 'XOR' 206 | case '%': 207 | case '%=': 208 | return 'MOD' 209 | case '<<': 210 | case '<<=': 211 | return 'SHL' 212 | case '>>': 213 | case '>>=': 214 | return 'SHR' 215 | default: 216 | throw new Error('Internal error.') 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/codeGenerator/astProcessor/endAsnProcessor.ts: -------------------------------------------------------------------------------- 1 | import { assertNotUndefined } from '../../repository/repository' 2 | import { CONTRACT, SC_FUNCTION } from '../../typings/contractTypes' 3 | import { END_ASN, LONG_TYPE_DEFINITION, MEMORY_SLOT, STRUCT_TYPE_DEFINITION } from '../../typings/syntaxTypes' 4 | import { createSimpleInstruction, createInstruction } from '../assemblyProcessor/createInstruction' 5 | import { GENCODE_ARGS, GENCODE_SOLVED_OBJECT } from '../codeGeneratorTypes' 6 | import utils from '../utils' 7 | import genCode from './genCode' 8 | 9 | export default function endAsnProcessor ( 10 | Program: CONTRACT, ScopeInfo: GENCODE_ARGS 11 | ) : GENCODE_SOLVED_OBJECT { 12 | let CurrentNode: END_ASN 13 | 14 | function endAsnProcessorMain () : GENCODE_SOLVED_OBJECT { 15 | CurrentNode = utils.assertAsnType('endASN', ScopeInfo.RemAST) 16 | switch (CurrentNode.Token.type) { 17 | case 'Constant': 18 | return constantProc() 19 | case 'Variable': 20 | return variableProc() 21 | case 'Keyword': 22 | return keywordProc() 23 | default: 24 | throw new Error(`Internal error at line: ${CurrentNode.Token.line}.`) 25 | } 26 | } 27 | 28 | function constantProc () : GENCODE_SOLVED_OBJECT { 29 | if (ScopeInfo.logicalOp) { 30 | if (ScopeInfo.revLogic === false) { 31 | if (CurrentNode.Token.value === '0000000000000000') { 32 | return { 33 | SolvedMem: utils.createVoidMemObj(), 34 | asmCode: createSimpleInstruction('Jump', ScopeInfo.jumpFalse) 35 | } 36 | } 37 | return { SolvedMem: utils.createVoidMemObj(), asmCode: '' } 38 | } 39 | if (CurrentNode.Token.value !== '0000000000000000') { 40 | return { 41 | SolvedMem: utils.createVoidMemObj(), 42 | asmCode: createSimpleInstruction('Jump', ScopeInfo.jumpTrue) 43 | } 44 | } 45 | return { SolvedMem: utils.createVoidMemObj(), asmCode: '' } 46 | } 47 | const RetMemObj = utils.createConstantMemObj(CurrentNode.Token.value) 48 | RetMemObj.line = CurrentNode.Token.line 49 | if (CurrentNode.Token.extValue === 'fixed') { 50 | RetMemObj.declaration = 'fixed' 51 | } 52 | return { SolvedMem: RetMemObj, asmCode: '' } 53 | } 54 | 55 | function variableProc () : GENCODE_SOLVED_OBJECT { 56 | if (ScopeInfo.logicalOp) { 57 | let { SolvedMem, asmCode } = genCode(Program, { 58 | RemAST: CurrentNode, 59 | logicalOp: false, 60 | revLogic: ScopeInfo.revLogic 61 | }) 62 | asmCode += createInstruction( 63 | Program, 64 | utils.genNotEqualToken(), 65 | SolvedMem, 66 | utils.createConstantMemObj(0), 67 | ScopeInfo.revLogic, 68 | ScopeInfo.jumpFalse, 69 | ScopeInfo.jumpTrue 70 | ) 71 | Program.Context.freeRegister(SolvedMem.address) 72 | return { SolvedMem: utils.createVoidMemObj(), asmCode: asmCode } 73 | } 74 | const retMemObj = Program.Context.getMemoryObjectByName( 75 | CurrentNode.Token.value, 76 | CurrentNode.Token.line, 77 | Program.Context.SentenceContext.isDeclaration 78 | ) 79 | if (Program.Context.SentenceContext.isRegisterSentence) { 80 | return registerProc(retMemObj) 81 | } 82 | return { SolvedMem: retMemObj, asmCode: '' } 83 | } 84 | 85 | function registerProc (retMemObj: MEMORY_SLOT) : GENCODE_SOLVED_OBJECT { 86 | const lastFreeRegister = Program.Context.registerInfo.filter(Reg => Reg.inUse === false).reverse()[0] 87 | if (lastFreeRegister === undefined || lastFreeRegister.Template.asmName === 'r0') { 88 | throw new Error(Program.Context.formatError(CurrentNode.Token.line, 89 | 'No more registers available. ' + 90 | `Increase the number with '#pragma maxAuxVars ${Program.Config.maxAuxVars + 1}' or try to reduce nested operations.`)) 91 | } 92 | lastFreeRegister.inUse = true 93 | lastFreeRegister.endurance = 'Scope' 94 | Program.Context.scopedRegisters.push(lastFreeRegister.Template.asmName) 95 | const varPrevAsmName = retMemObj.asmName 96 | const motherMemory = assertNotUndefined(Program.memory.find(obj => obj.asmName === retMemObj.asmName), 'Internal error') 97 | retMemObj.address = lastFreeRegister.Template.address 98 | retMemObj.asmName = lastFreeRegister.Template.asmName 99 | motherMemory.address = retMemObj.address 100 | motherMemory.asmName = retMemObj.asmName 101 | if (Program.Config.verboseAssembly) { 102 | return { SolvedMem: retMemObj, asmCode: `^comment scope ${lastFreeRegister.Template.asmName}:${varPrevAsmName}\n` } 103 | } 104 | return { SolvedMem: retMemObj, asmCode: '' } 105 | } 106 | 107 | function keywordProc () : GENCODE_SOLVED_OBJECT { 108 | const CurrentFunction: SC_FUNCTION | undefined = Program.functions[Program.Context.currFunctionIndex] 109 | if (ScopeInfo.logicalOp) { 110 | throw new Error(Program.Context.formatError(CurrentNode.Token.line, 111 | `Cannot use of keyword '${CurrentNode.Token.value}' in logical statements.`)) 112 | } 113 | switch (CurrentNode.Token.value) { 114 | case 'break': 115 | case 'continue': 116 | case 'asm': 117 | case 'exit': 118 | case 'halt': 119 | case 'sleep': 120 | return { 121 | SolvedMem: utils.createVoidMemObj(), 122 | asmCode: createInstruction(Program, CurrentNode.Token) 123 | } 124 | case 'void': 125 | throw new Error(Program.Context.formatError(CurrentNode.Token.line, 126 | "Invalid use of keyword 'void'.")) 127 | case 'long': { 128 | const LongTypeDefinition = Program.typesDefinitions.find( 129 | Obj => Obj.type === 'long' 130 | ) as LONG_TYPE_DEFINITION | undefined 131 | return { 132 | SolvedMem: assertNotUndefined(LongTypeDefinition).MemoryTemplate, 133 | asmCode: '' 134 | } 135 | } 136 | case 'struct': { 137 | let StructTypeDefinition = Program.typesDefinitions.find( 138 | Obj => Obj.type === 'struct' && Obj.name === CurrentNode.Token.extValue 139 | ) as STRUCT_TYPE_DEFINITION | undefined 140 | if (StructTypeDefinition === undefined && CurrentFunction !== undefined) { 141 | StructTypeDefinition = Program.typesDefinitions.find( 142 | Obj => Obj.type === 'struct' && Obj.name === CurrentFunction?.name + '_' + CurrentNode.Token.extValue 143 | ) as STRUCT_TYPE_DEFINITION | undefined 144 | } 145 | if (StructTypeDefinition === undefined) { 146 | throw new Error(Program.Context.formatError(CurrentNode.Token.line, 147 | `Struct type definition for '${CurrentNode.Token.extValue}' not found.`)) 148 | } 149 | return { 150 | SolvedMem: StructTypeDefinition.MemoryTemplate, 151 | asmCode: '' 152 | } 153 | } 154 | case 'return': 155 | // this is 'return;' 156 | if (CurrentFunction === undefined) { 157 | throw new Error(Program.Context.formatError(CurrentNode.Token.line, 158 | "Can not use 'return' in global statements.")) 159 | } 160 | if (CurrentFunction.declaration !== 'void') { 161 | throw new Error(Program.Context.formatError(CurrentNode.Token.line, 162 | `Function '${CurrentFunction.name}'` + 163 | ` must return a '${CurrentFunction.declaration}' value.`)) 164 | } 165 | if (CurrentFunction.name === 'main' || CurrentFunction.name === 'catch') { 166 | return { 167 | SolvedMem: utils.createVoidMemObj(), 168 | asmCode: createSimpleInstruction('exit') 169 | } 170 | } 171 | return { 172 | SolvedMem: utils.createVoidMemObj(), 173 | asmCode: createInstruction(Program, CurrentNode.Token) 174 | } 175 | default: 176 | throw new Error(`Internal error at line: ${CurrentNode.Token.line}.`) 177 | } 178 | } 179 | 180 | return endAsnProcessorMain() 181 | } 182 | -------------------------------------------------------------------------------- /src/context.ts: -------------------------------------------------------------------------------- 1 | import { GLOBAL_CONTEXT } from './codeGenerator/codeGeneratorTypes' 2 | import { assertNotUndefined, deepCopy } from './repository/repository' 3 | import { CONTRACT } from './typings/contractTypes' 4 | import { DECLARATION_TYPES, MEMORY_SLOT } from './typings/syntaxTypes' 5 | 6 | export function createContext (Program: CONTRACT) : GLOBAL_CONTEXT { 7 | function detectAndSetNotInitialized (Memory: MEMORY_SLOT, line: string, isInitialization: boolean) { 8 | if (Program.Context.SentenceContext.isLeftSideOfAssignment || Memory.hexContent) { 9 | Memory.isSet = true 10 | return 11 | } 12 | if (isInitialization) { 13 | return 14 | } 15 | Program.Context.warnings.push('Warning! ' + Program.Context.formatError(line, 16 | `Variable '${Memory.name}' is used but not initialized.`)) 17 | Memory.isSet = true // No more warning for same variable 18 | } 19 | 20 | return { 21 | registerInfo: [], 22 | isTemp (loc: number) : boolean { 23 | if (loc === -1) return false 24 | const id = this.registerInfo.find(OBJ => OBJ.Template.address === loc) 25 | if (id === undefined) { 26 | return false 27 | } 28 | if (this.scopedRegisters.find(items => items === id.Template.asmName)) { 29 | // It is a register, but scoped. Do not mess!!! 30 | return false 31 | } 32 | return true 33 | }, 34 | getNewRegister (line: string): MEMORY_SLOT { 35 | const id = this.registerInfo.find(OBJ => OBJ.inUse === false) 36 | if (id === undefined) { 37 | throw new Error(`At line: ${line}. ` + 38 | 'No more registers available. ' + 39 | `Increase the number with '#pragma maxAuxVars ${Program.Config.maxAuxVars + 1}' or try to reduce nested operations.`) 40 | } 41 | id.inUse = true 42 | return deepCopy(id.Template) 43 | }, 44 | freeRegister (loc: number|undefined): void { 45 | if (loc === undefined || loc === -1) { 46 | return 47 | } 48 | const RegInfo = this.registerInfo.find(OBJ => OBJ.Template.address === loc) 49 | if (RegInfo === undefined) return 50 | if (RegInfo.endurance === 'Scope') return 51 | RegInfo.inUse = false 52 | }, 53 | latestLoopId: [], 54 | jumpId: 0, 55 | assemblyCode: '', 56 | warnings: [], 57 | errors: [], 58 | currFunctionIndex: -1, 59 | currSourceLine: 0, 60 | formatError (line: string, message: string) : string { 61 | let error = `At line: ${line}. ${message}\n` 62 | const lineNo = Number(line.split(':')[0]) 63 | const colNo = Number(line.split(':')[1]) 64 | if (line === undefined || line === '0:0' || Number.isNaN(lineNo) || Number.isNaN(colNo)) { 65 | return error 66 | } 67 | error += ' |' + Program.sourceLines[lineNo - 1] + '\n' 68 | error += ' |' + '^'.padStart(colNo) 69 | return error 70 | }, 71 | scopedRegisters: [], 72 | getNewJumpID: function () { 73 | // Any changes here, also change function auxvarsGetNewJumpID 74 | this.jumpId++ 75 | return this.jumpId.toString(36) 76 | }, 77 | getLatestLoopID: function () { 78 | // error check must be in code! 79 | return this.latestLoopId[this.latestLoopId.length - 1] 80 | }, 81 | getLatestPureLoopID: function () { 82 | // error check must be in code! 83 | return this.latestLoopId.reduce((previous, current) => { 84 | if (current.includes('loop')) { 85 | return current 86 | } 87 | return previous 88 | }, '') 89 | }, 90 | printFreeRegisters () { 91 | let registers = 'r0' 92 | for (let i = 1; i < Program.Config.maxAuxVars; i++) { 93 | if (this.scopedRegisters.findIndex(item => item === `r${i}`) === -1) { 94 | registers += `,r${i}` 95 | } 96 | } 97 | this.assemblyCode += `^comment scope ${registers}\n` 98 | }, 99 | startScope (scopeName: string) { 100 | this.scopedRegisters.push(scopeName) 101 | if (Program.Config.verboseScope) { 102 | this.printFreeRegisters() 103 | } 104 | }, 105 | stopScope: function (scopeName: string) { 106 | let liberationNeeded: string 107 | do { 108 | liberationNeeded = assertNotUndefined(this.scopedRegisters.pop(), 'Internal error') 109 | if (/^r\d$/.test(liberationNeeded)) { 110 | const motherMemory = assertNotUndefined(Program.memory.find(obj => 111 | obj.asmName === liberationNeeded && 112 | obj.type !== 'register' 113 | ), 'Internal error') 114 | motherMemory.address = -1 115 | motherMemory.asmName = '' 116 | const Reg = assertNotUndefined(this.registerInfo.find(Item => Item.Template.asmName === liberationNeeded)) 117 | Reg.inUse = false 118 | Reg.endurance = 'Standard' 119 | } 120 | } while (liberationNeeded !== scopeName) 121 | if (Program.Config.verboseScope) { 122 | this.printFreeRegisters() 123 | } 124 | }, 125 | getMemoryObjectByName ( 126 | varName: string, line: string, varDeclaration: DECLARATION_TYPES = '' 127 | ) : MEMORY_SLOT { 128 | let MemFound: MEMORY_SLOT | undefined 129 | if (this.currFunctionIndex >= 0) { // find function scope variable 130 | MemFound = Program.memory.find(obj => { 131 | return obj.name === varName && obj.scope === Program.functions[this.currFunctionIndex].name 132 | }) 133 | } 134 | if (MemFound === undefined) { 135 | // do a global scope search 136 | MemFound = Program.memory.find(obj => obj.name === varName && obj.scope === '') 137 | } 138 | if (MemFound === undefined) { 139 | throw new Error(Program.Context.formatError(line, `Using variable '${varName}' before declaration.`)) 140 | } 141 | if (MemFound.toBeRegister && MemFound.asmName === '') { 142 | throw new Error(`At line: ${line}. Using variable '${varName}' out of scope!`) 143 | } 144 | if (!MemFound.isSet) { 145 | detectAndSetNotInitialized(MemFound, line, varDeclaration !== '') 146 | } 147 | if (varDeclaration !== '') { // we are in declarations sentence 148 | MemFound.isDeclared = true 149 | return deepCopy(MemFound) 150 | } 151 | return deepCopy(MemFound) 152 | }, 153 | getMemoryObjectByLocation (loc: number|bigint|string, line: string): MEMORY_SLOT { 154 | let addr:number 155 | switch (typeof loc) { 156 | case 'number': addr = loc; break 157 | case 'string': addr = parseInt(loc, 16); break 158 | default: addr = Number(loc) 159 | } 160 | const FoundMemory = Program.memory.find(obj => obj.address === addr) 161 | if (FoundMemory === undefined) { 162 | throw new Error(`At line: ${line}. No variable found at address '${addr}'.`) 163 | } 164 | if (!FoundMemory.isSet) { 165 | detectAndSetNotInitialized(FoundMemory, line, false) 166 | } 167 | return deepCopy(FoundMemory) 168 | }, 169 | SentenceContext: { 170 | isDeclaration: '', 171 | isLeftSideOfAssignment: false, 172 | leftSideReserved: -1, 173 | isConstSentence: false, 174 | isRegisterSentence: false, 175 | hasVoidArray: false, 176 | postOperations: '', 177 | getAndClearPostOperations: function () { 178 | const ret = this.postOperations 179 | this.postOperations = '' 180 | return ret 181 | } 182 | }, 183 | TokenizerDetection: { 184 | hasFixed: false, 185 | hasAutoCounter: false 186 | }, 187 | ShaperContext: { 188 | latestLoopId: [], 189 | isFunctionArgument: false, 190 | currentScopeName: '', 191 | currentPrefix: '' 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/__tests__/castings.a.spec.ts: -------------------------------------------------------------------------------- 1 | import { SmartC } from '../smartc' 2 | 3 | describe('Castings arithmetic', () => { 4 | it('should compile: special verification on long <=> fixed', () => { 5 | const code = '#pragma optimizationLevel 0\nfixed fa, fb; long la, lb;\n la = lb + (long)fa; fa = la + (fixed)lb;' 6 | const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare f100000000\n^const SET @f100000000 #0000000005f5e100\n^declare fa\n^declare fb\n^declare la\n^declare lb\n\nSET @la $fa\nDIV @la $f100000000\nADD @la $lb\nSET @fa $lb\nMUL @fa $f100000000\nSET @r0 $la\nMUL @r0 $f100000000\nADD @fa $r0\nFIN\n' 7 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 8 | compiler.compile() 9 | expect(compiler.getAssemblyCode()).toBe(assembly) 10 | }) 11 | it('should compile: from void to all', () => { 12 | const code = ' #pragma optimizationLevel 0\n void *pv; long l, *pl; fixed f, *pf; struct TEST { long aa, bb; } s, *ps;\n l = (long)(); f = (fixed)(); pv = (void *)(); pl = (long *)(); pf = (fixed *)(); ps = (struct BB *)();' 13 | const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare f100000000\n^const SET @f100000000 #0000000005f5e100\n^declare pv\n^declare l\n^declare pl\n^declare f\n^declare pf\n^declare s_aa\n^declare s_bb\n^declare ps\n\nCLR @l\nCLR @f\nCLR @pv\nCLR @pl\nCLR @pf\nCLR @ps\nFIN\n' 14 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 15 | compiler.compile() 16 | expect(compiler.getAssemblyCode()).toBe(assembly) 17 | }) 18 | it('should compile: from all to void', () => { 19 | const code = ' #pragma optimizationLevel 0\nvoid *pv; long l, *pl; fixed f, *pf; struct TEST { long aa, bb; } s, *ps;\n (void)(l+1); (void)(f+1.2);' 20 | const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare f100000000\n^const SET @f100000000 #0000000005f5e100\n^declare pv\n^declare l\n^declare pl\n^declare f\n^declare pf\n^declare s_aa\n^declare s_bb\n^declare ps\n\nSET @r0 $l\nINC @r0\nSET @r0 #0000000007270e00\nADD @r0 $f\nFIN\n' 21 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 22 | compiler.compile() 23 | expect(compiler.getAssemblyCode()).toBe(assembly) 24 | }) 25 | it('should compile: from long to all', () => { 26 | const code = '#pragma optimizationLevel 0\nvoid *pv;long l, *pl;fixed f, *pf;struct TEST { long aa, bb; } s, *ps;\n l = (long)l; f = (fixed)l; pv = (void *)l; pl = (long *)l; pf = (fixed *)l; ps = (struct BB *)l;' 27 | const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare f100000000\n^const SET @f100000000 #0000000005f5e100\n^declare pv\n^declare l\n^declare pl\n^declare f\n^declare pf\n^declare s_aa\n^declare s_bb\n^declare ps\n\nSET @f $l\nMUL @f $f100000000\nSET @pv $l\nSET @pl $l\nSET @pf $l\nSET @ps $l\nFIN\n' 28 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 29 | compiler.compile() 30 | expect(compiler.getAssemblyCode()).toBe(assembly) 31 | }) 32 | test('should throw: from fixed to pointers', () => { 33 | expect(() => { 34 | const code = '#pragma optimizationLevel 0\nvoid *pv;long l, *pl;fixed f, *pf;struct TEST { long aa, bb; } s, *ps;\n pv = (void *)f;' 35 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 36 | compiler.compile() 37 | }).toThrowError(/^At line/) 38 | }) 39 | it('should compile: from struct to all', () => { 40 | const code = '#pragma optimizationLevel 0\nvoid *pv;long l, *pl;fixed f, *pf;struct TEST { long aa, bb; } s, *ps;\n pv = (void *)s; pl = (long *)s; pf = (fixed *)s; ps = (struct BB *)s;' 41 | const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare f100000000\n^const SET @f100000000 #0000000005f5e100\n^declare pv\n^declare l\n^declare pl\n^declare f\n^declare pf\n^declare s_aa\n^declare s_bb\n^declare ps\n\nSET @pv #0000000000000009\nSET @pl #0000000000000009\nSET @pf #0000000000000009\nSET @ps #0000000000000009\nFIN\n' 42 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 43 | compiler.compile() 44 | expect(compiler.getAssemblyCode()).toBe(assembly) 45 | }) 46 | test('should throw: from struct to long', () => { 47 | expect(() => { 48 | const code = '#pragma optimizationLevel 0\nvoid *pv;long l, *pl;fixed f, *pf;struct TEST { long aa, bb; } s, *ps;\n l = (long)s;' 49 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 50 | compiler.compile() 51 | }).toThrowError(/^At line/) 52 | }) 53 | test('should throw: from struct to fixed', () => { 54 | expect(() => { 55 | const code = '#pragma optimizationLevel 0\nvoid *pv;long l, *pl;fixed f, *pf;struct TEST { long aa, bb; } s, *ps;\n f = (fixed)s;' 56 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 57 | compiler.compile() 58 | }).toThrowError(/^At line/) 59 | }) 60 | it('should compile: from pointer to all', () => { 61 | const code = '#pragma optimizationLevel 0\nvoid *pv;long l, *pl;fixed f, *pf;struct TEST { long aa, bb; } s, *ps;\n l = (long)pl; pv = (void *)pl; pl = (long *)pl; pf = (fixed *)pl; ps = (struct BB *)pl;' 62 | const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare f100000000\n^const SET @f100000000 #0000000005f5e100\n^declare pv\n^declare l\n^declare pl\n^declare f\n^declare pf\n^declare s_aa\n^declare s_bb\n^declare ps\n\nSET @l $pl\nSET @pv $pl\nSET @pf $pl\nSET @ps $pl\nFIN\n' 63 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 64 | compiler.compile() 65 | expect(compiler.getAssemblyCode()).toBe(assembly) 66 | }) 67 | test('should throw: from pointer to fixed', () => { 68 | expect(() => { 69 | const code = '#pragma optimizationLevel 0\nvoid *pv;long l, *pl;fixed f, *pf;struct TEST { long aa, bb; } s, *ps;\n f = (fixed)pv;' 70 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 71 | compiler.compile() 72 | }).toThrowError(/^At line/) 73 | }) 74 | it('should compile: complex hack', () => { 75 | const code = '#pragma optimizationLevel 0\nfixed fa, *pf; long la, *pl; la = *((long*)(&fa)); fa = *((fixed*)(&la));' 76 | const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare f100000000\n^const SET @f100000000 #0000000005f5e100\n^declare fa\n^declare pf\n^declare la\n^declare pl\n\nSET @la $fa\nSET @fa $la\nFIN\n' 77 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 78 | compiler.compile() 79 | expect(compiler.getAssemblyCode()).toBe(assembly) 80 | }) 81 | it('should compile: casting hack on returning function', () => { 82 | const code = '#pragma optimizationLevel 0\n#include APIFunctions\nfixed a, b; a = b + (*(fixed *)(&Get_A1()));' 83 | const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare f100000000\n^const SET @f100000000 #0000000005f5e100\n^declare a\n^declare b\n\nFUN @r0 get_A1\nSET @a $b\nADD @a $r0\nFIN\n' 84 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 85 | compiler.compile() 86 | expect(compiler.getAssemblyCode()).toBe(assembly) 87 | }) 88 | }) 89 | 90 | describe('Castings logical', () => { 91 | it('should compile: all permitted types', () => { 92 | const code = '#pragma optimizationLevel 0\nlong la, lb, *pl; fixed fa, fb, *pf; void * pv;\n if (la > fb) la++; if (fa > lb) la++; if (fa >= fb) la++; if (la < pl) la++; if (la <= pf) la++; if (pv == pl) la++; if (pf != pl) la++;' 93 | const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare f100000000\n^const SET @f100000000 #0000000005f5e100\n^declare la\n^declare lb\n^declare pl\n^declare fa\n^declare fb\n^declare pf\n^declare pv\n\nSET @r0 $la\nMUL @r0 $f100000000\nBLE $r0 $fb :__if1_endif\n__if1_start:\nINC @la\n__if1_endif:\nSET @r0 $lb\nMUL @r0 $f100000000\nBLE $fa $r0 :__if2_endif\n__if2_start:\nINC @la\n__if2_endif:\nBLT $fa $fb :__if3_endif\n__if3_start:\nINC @la\n__if3_endif:\nBGE $la $pl :__if4_endif\n__if4_start:\nINC @la\n__if4_endif:\nBGT $la $pf :__if5_endif\n__if5_start:\nINC @la\n__if5_endif:\nBNE $pv $pl :__if6_endif\n__if6_start:\nINC @la\n__if6_endif:\nBEQ $pf $pl :__if7_endif\n__if7_start:\nINC @la\n__if7_endif:\nFIN\n' 94 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 95 | compiler.compile() 96 | expect(compiler.getAssemblyCode()).toBe(assembly) 97 | }) 98 | test('should throw: pointer vs fixed', () => { 99 | expect(() => { 100 | const code = 'long la, lb, *pl; fixed fa, fb, *pf; void * pv;\n if (la > fb) la++; if (fa > pv) la++;' 101 | const compiler = new SmartC({ language: 'C', sourceCode: code }) 102 | compiler.compile() 103 | }).toThrowError(/^At line/) 104 | }) 105 | }) 106 | -------------------------------------------------------------------------------- /docs/3-Learning-with-examples.md: -------------------------------------------------------------------------------- 1 | [Back](./README.md) 2 | 3 | # Lessons and examples to create smart contracts in Signum network 4 | Following guide will show examples with progressive complexity and comments how they works. It is expected that you know C language. It is a good idea to read all docs from SmartC. If you plan to be expert, read ciyam official documentation available [here](https://ciyam.org/at/) and Signum [SIP-37](https://github.com/signum-network/SIPs/blob/master/SIP/sip-37.md), [SIP-38](https://github.com/signum-network/SIPs/blob/master/SIP/sip-38.md), [SIP-39](https://github.com/signum-network/SIPs/blob/master/SIP/sip-39.md) for major changes introduced in JUN/2022. There is also some videos compiling these examples at my [personal Youtube channel](https://www.youtube.com/playlist?list=PLyu0NNtb1eg3Gcg2JCrOle8MjtuFPb-Gi). 5 | 6 |
7 | 8 | 9 | ## Always Running, doing nothing 10 | 11 | 12 | ```c 13 | #program name alwaysRuning 14 | #program description Always Running, doing nothing 15 | #program activationAmount 0 16 | #pragma maxAuxVars 0 17 | 18 | while (true) { 19 | sleep; 20 | } 21 | ``` 22 | * This contract has no functions, API, nor variable declared. 23 | * Macro `#pragma` can set some specific behaviour of compiler, in this case it will set compiler to use no auxiliary variable (they act as registers for operations). Default value is 3, but here we do not need any. 24 | * Only one global statement, `while (true)` is used to make an infinite loop. 25 | * Keyword `sleep 1` will stop the contract process at current block and resumes execution at the next block. 26 | * It will keep in this loop until there is no more balance at the contract, then it will be frozen until it receives more balance. 27 | * Activation amount zero means that the contract will be always active, even if there was a `exit` statement. 28 |
29 |
30 | 31 | 32 | ## Counting transactions, easy way 33 | 34 | 35 | ```c 36 | #program name CountingTxDelayed 37 | #program description Counting transactions, easy way 38 | #program activationAmount 0.1 39 | 40 | void main(void) { 41 | long counter, txid; 42 | getNextTx(); 43 | counter++; 44 | } 45 | ``` 46 | * The `main` function is the entry point when contract gets an activation. Contract can be finished in this function via `return`, `exit` or at the end of function. 47 | * If two transactions are received by this contract, the first one will be processed and the contract will enter finished state. In the next block it will be activated again with the second transaction that was not processed in previous block height. This means, if this contract receives 10 messages at some block, it will take 10 blocks to finish counting them. 48 | * When the contract is created, all memory is set to zero. So variable counter will start from zero and keep increasing every block it has received TXs. 49 | * Activation amount 0.1 means that the contract will only count the transactions that send at least this amount. If a transaction with .09999999 Signa is received, the balance will the added to the contract but it will not be counted. 50 |
51 |
52 | 53 | 54 | ## Counting transactions without delay. 55 | 56 | 57 | ```c 58 | #program name CountingTx 59 | #program description Counting transactions without delay 60 | #program activationAmount 0.1 61 | 62 | long counter; 63 | 64 | void main(void) { 65 | long txid; 66 | while ((txid = getNextTx()) != 0) { 67 | // Process transaction in a specific function 68 | processTX(); 69 | } 70 | // Optional function to make something after all transactions processed 71 | // clean_up(); 72 | } 73 | 74 | void processTX(void){ 75 | const counter = 10; 76 | counter++; 77 | } 78 | ``` 79 | * It is presented the built-in function `getNextTx()` that will return the transaction Id of the next transaction. It stores internally the timestamp of last received transaction and returns zero if there is no more pending transactions. 80 | * The while loop will be executed for all pending messages. When txid is zero, the contract can be finished. 81 | * Counter value will be set to 10 during contract deployment (keyword const!). Then it will be increased for each new valid tx received. Const expressions are 'executed' only at the deployment. 82 | * counter is global variable just to show how to declare it. It is more effective to have a global variable than sending it to functions. If a variable is used only in one function, use the local scope. 83 |
84 |
85 | 86 | 87 | ## Echo signa 88 | 89 | 90 | ```c 91 | #program name EchoSigna 92 | #program description Returns the received amount to the sender. 93 | #program activationAmount 0.5 94 | 95 | while (true) { 96 | long txid; 97 | while ((txid = getNextTx()) != 0) { 98 | sendAmount(getAmount(txid), getSender(txid)) 99 | } 100 | // After all transactions processed 101 | sendBalance(getCreator()); 102 | } 103 | ``` 104 | * `#program activationAmount 0.5` ensures that only transactions with an amount greater or equal 0.5 signa will be processed. The returned amount will be only the value above activation amount. 105 | * For every transaction processed, some unspent balance will build up in the contract. To avoid this situation, after all transactions were processed in current block, the contract sends all remaining balance to the creator. 106 | * `getAmount`: returns the amount in NQT of a given transaction id. 107 | * `getSender`: returns the account id of sender of a given transaction id. 108 | * `sendAmount`: sends a given amount of Signa to a recipient. 109 | * `sendBalance`: sends all contract balance to a recipient. The execution is halted after the transaction is sent. 110 |
111 |
112 | 113 | 114 | ## Echo message 115 | 116 | 117 | ```c 118 | #program name EchoMessage 119 | #program description Reads first page of message (32 bytes) and sends back to sender. \ 120 | Also sends back 95% of the amount sent. 121 | #program activationAmount 0.5 122 | 123 | struct TXINFO { 124 | long txid; 125 | long sender; 126 | fixed amount; 127 | long message[4]; 128 | } currentTX; 129 | 130 | void main () { 131 | while ((currentTX.txid = getNextTx()) != 0) { 132 | getDetails(); 133 | processTX(); 134 | } 135 | // After all transactions processed 136 | // cleanUp(); 137 | } 138 | 139 | void getDetails() { 140 | currentTX.sender = getSender(currentTX.txid); 141 | currentTX.amount = getAmountFx(currentTX.txid); 142 | readMessage(currentTX.txid, 0, currentTX.message); 143 | } 144 | 145 | void processTX() { 146 | sendAmountAndMessageFx( 147 | currentTX.amount * 0.95, 148 | currentTX.message, 149 | currentTX.sender 150 | ); 151 | } 152 | ``` 153 | * In this contract the Signa amount is handled with fixed point variables. They have the same 8 decimal numbers the people is used to and can be calculated with operators + - * / >> and <<. Useful to make calculations. All built-in functions that handle Signa balance have a fixed point version that ends with Fx. 154 | * Balance will build up in the contract because there is no cleanUp function defined. 155 |
156 |
157 | 158 | 159 | ## Echo token 160 | 161 | 162 | ```c 163 | #program name EchoToken 164 | #program description Returns the assets received. 165 | #program activationAmount 0.5 166 | 167 | struct TXINFO { 168 | long txid; 169 | long sender; 170 | long assets[4]; 171 | } currentTX; 172 | 173 | void main () { 174 | while ((currentTX.txid = getNextTx()) != 0) { 175 | getDetails(); 176 | processTX(); 177 | } 178 | // After all transactions processed 179 | cleanUp(); 180 | } 181 | 182 | void getDetails() { 183 | currentTX.sender = getSender(currentTX.txid); 184 | readAssets(currentTX.txid, currentTX.assets); 185 | } 186 | 187 | void processTX() { 188 | for (long i = 0; i < 4; i++) { 189 | if (currentTX.assets[i] == 0) { 190 | // Zero means no more assets in incoming transaction 191 | return; 192 | } 193 | sendQuantity( 194 | getQuantity(currentTX.txid, currentTX.assets[i]), 195 | currentTX.assets[i], 196 | currentTX.sender 197 | ); 198 | } 199 | } 200 | 201 | void cleanUp() { 202 | fixed excessBalance; 203 | excessBalance = getCurrentBalanceFx() - 0.5; 204 | if (excessBalance > 0) { 205 | sendAmountFx(excessBalance, getCreator()); 206 | } 207 | } 208 | ``` 209 | * Assets amount is called "quantity" and always returned as long (QNT), because it is not possible to know how many decimals they have. 210 | * To avoid balance build up in the contract, any Signa amount and unspent activation amount is sent to creator if more than 0.5 Signa. 211 | * If `sendBalance` is used in cleanUp, the contract would also stop execution. But the next transaction will reactivate the contract and it will reach the end of void function, so halting again. Only when another transaction is received, the contract will start again at the main function and process the two enqueued transactions. 212 |
213 | 214 | [Back](./README.md) 215 | -------------------------------------------------------------------------------- /src/codeGenerator/astProcessor/functionSolver.ts: -------------------------------------------------------------------------------- 1 | import { assertExpression, assertNotUndefined } from '../../repository/repository' 2 | import { CONTRACT, SC_FUNCTION } from '../../typings/contractTypes' 3 | import { LOOKUP_ASN, AST, MEMORY_SLOT } from '../../typings/syntaxTypes' 4 | import { createBuiltinInstruction } from '../assemblyProcessor/builtinToAsm' 5 | import { 6 | createSimpleInstruction, createInstruction, createAPICallInstruction, forceSetMemFromR0 7 | } from '../assemblyProcessor/createInstruction' 8 | import { GENCODE_ARGS, GENCODE_SOLVED_OBJECT } from '../codeGeneratorTypes' 9 | import utils from '../utils' 10 | import genCode from './genCode' 11 | 12 | export default function functionSolver ( 13 | Program: CONTRACT, ScopeInfo: GENCODE_ARGS 14 | ) : GENCODE_SOLVED_OBJECT { 15 | let CurrentNode: LOOKUP_ASN 16 | 17 | function functionSolverMain (): GENCODE_SOLVED_OBJECT { 18 | CurrentNode = utils.assertAsnType('lookupASN', ScopeInfo.RemAST) 19 | assertExpression(CurrentNode.Token.type === 'Function') 20 | const fnName = assertNotUndefined(CurrentNode.Token.extValue) 21 | const FnToCall = Program.functions.find(val => val.name === fnName) 22 | const ApiToCall = Program.Global.APIFunctions.find(val => val.name === fnName) 23 | const BuiltInToCall = Program.Global.BuiltInFunctions.find(val => val.name === fnName) 24 | const subSentences = utils.splitASTOnDelimiters(assertNotUndefined(CurrentNode.FunctionArgs)) 25 | if (FnToCall) { 26 | return userFunctionSolver(FnToCall, subSentences) 27 | } 28 | if (BuiltInToCall) { 29 | return internalFunctionSolver('builtin', BuiltInToCall, subSentences) 30 | } 31 | if (ApiToCall) { 32 | return internalFunctionSolver('api', ApiToCall, subSentences) 33 | } 34 | throw new Error(Program.Context.formatError(CurrentNode.Token.line, `Function '${fnName}' not declared.`)) 35 | } 36 | 37 | function userFunctionSolver (FunctionToCall: SC_FUNCTION, rawArgs: AST[]) : GENCODE_SOLVED_OBJECT { 38 | let FnRetObj: MEMORY_SLOT 39 | let returnAssemblyCode = '' 40 | // It is regular function call 41 | let isRecursive = false 42 | if (Program.Context.currFunctionIndex !== -1 && FunctionToCall.name === Program.functions[Program.Context.currFunctionIndex].name) { 43 | isRecursive = true 44 | // stack current scope variables 45 | Program.memory.filter(OBJ => OBJ.scope === FunctionToCall.name && OBJ.address > 0).reverse().forEach(MEM => { 46 | returnAssemblyCode += createSimpleInstruction('Push', MEM.asmName) 47 | }) 48 | } 49 | // Check function arguments 50 | if (rawArgs[0].type === 'nullASN') { 51 | rawArgs.pop() 52 | } 53 | if (rawArgs.length !== FunctionToCall.argsMemObj.length) { 54 | throw new Error(Program.Context.formatError(CurrentNode.Token.line, 55 | ` Wrong number of arguments for function '${FunctionToCall.name}'.` + 56 | ` It must have '${FunctionToCall.argsMemObj.length}' args.`)) 57 | } 58 | // Solve caller arguments and set to callee arguments 59 | for (let i = rawArgs.length - 1; i >= 0; i--) { 60 | const ArgGenObj = genCode(Program, { 61 | RemAST: rawArgs[i], 62 | logicalOp: false, 63 | revLogic: false 64 | }) 65 | const fnArg = FunctionToCall.argsMemObj[i] 66 | if (utils.isNotValidDeclarationOp(fnArg.declaration, ArgGenObj.SolvedMem)) { 67 | throw new Error(Program.Context.formatError(CurrentNode.Token.line, 68 | ` Type of function argument #${i + 1} is different from variable: ` + 69 | ` Expecting '${fnArg.declaration}', got '${ArgGenObj.SolvedMem.declaration}'.`)) 70 | } 71 | if (ArgGenObj.SolvedMem.size !== 1 && ArgGenObj.SolvedMem.Offset === undefined) { 72 | throw new Error(Program.Context.formatError(CurrentNode.Token.line, 73 | ' Overflow in argument size.')) 74 | } 75 | returnAssemblyCode += ArgGenObj.asmCode 76 | returnAssemblyCode += createInstruction( 77 | Program, 78 | utils.genAssignmentToken(CurrentNode.Token.line), 79 | fnArg, 80 | ArgGenObj.SolvedMem 81 | ) 82 | Program.Context.freeRegister(ArgGenObj.SolvedMem.address) 83 | } 84 | // Save registers currently in use in stack. Function execution may overwrite them 85 | const registerStack = Program.Context.registerInfo.filter(OBJ => OBJ.inUse === true).reverse() 86 | registerStack.forEach(OBJ => { 87 | if (OBJ.Template.address === Program.Context.SentenceContext.leftSideReserved) return 88 | returnAssemblyCode += createSimpleInstruction('Push', OBJ.Template.asmName) 89 | }) 90 | // Create instruction 91 | if (FunctionToCall.isInline) { 92 | returnAssemblyCode += `%inline.${FunctionToCall.name}%\n` 93 | } else { 94 | returnAssemblyCode += createSimpleInstruction('Function', FunctionToCall.name) 95 | } 96 | // Pop return value from stack 97 | if (FunctionToCall.declaration === 'void') { 98 | FnRetObj = utils.createVoidMemObj() 99 | } else { 100 | FnRetObj = Program.Context.getNewRegister() 101 | FnRetObj.declaration = FunctionToCall.declaration 102 | FnRetObj.typeDefinition = FunctionToCall.typeDefinition 103 | returnAssemblyCode += forceSetMemFromR0(Program, FnRetObj, CurrentNode.Token.line) 104 | } 105 | // Load registers again 106 | registerStack.reverse() 107 | registerStack.forEach(OBJ => { 108 | if (OBJ.Template.address === Program.Context.SentenceContext.leftSideReserved) return 109 | returnAssemblyCode += createSimpleInstruction('Pop', OBJ.Template.asmName) 110 | }) 111 | if (isRecursive) { 112 | // unstack current scope variables 113 | Program.memory.filter(OBJ => OBJ.scope === FunctionToCall.name && OBJ.address > 0).forEach(MEM => { 114 | returnAssemblyCode += createSimpleInstruction('Pop', MEM.asmName) 115 | }) 116 | } 117 | return { SolvedMem: FnRetObj, asmCode: returnAssemblyCode } 118 | } 119 | 120 | function internalFunctionSolver (type: 'builtin' | 'api', ifnToCall: SC_FUNCTION, rawArgs: AST[]) : GENCODE_SOLVED_OBJECT { 121 | let FnRetObj: MEMORY_SLOT 122 | const processedArgs: MEMORY_SLOT [] = [] 123 | let returnAssemblyCode = '' 124 | if (ifnToCall.declaration === 'void' || ifnToCall.name === 'bcftol' || ifnToCall.name === 'bcltof') { 125 | FnRetObj = utils.createVoidMemObj() 126 | } else { 127 | FnRetObj = Program.Context.getNewRegister() // reserve tempvar for return type 128 | FnRetObj.declaration = ifnToCall.declaration 129 | } 130 | if (rawArgs[0].type === 'nullASN') { 131 | rawArgs.pop() 132 | } 133 | if (rawArgs.length !== ifnToCall.argsMemObj.length) { 134 | throw new Error(Program.Context.formatError(CurrentNode.Token.line, 135 | ` Wrong number of arguments for function '${ifnToCall.name}'.` + 136 | ` It must have '${ifnToCall.argsMemObj.length}' args.`)) 137 | } 138 | rawArgs.forEach((RawSentence, idx) => { 139 | const ArgGenObj = genCode(Program, { 140 | RemAST: RawSentence, 141 | logicalOp: false, 142 | revLogic: false 143 | }) 144 | returnAssemblyCode += ArgGenObj.asmCode 145 | if (utils.isNotValidDeclarationOp(ifnToCall.argsMemObj[idx].declaration, ArgGenObj.SolvedMem)) { 146 | throw new Error(Program.Context.formatError(CurrentNode.Token.line, 147 | ` Type of API Function argument #${idx + 1} is different from variable. ` + 148 | ` Expecting '${ifnToCall.argsMemObj[idx].declaration}', got '${ArgGenObj.SolvedMem.declaration}'.`)) 149 | } 150 | if (ArgGenObj.SolvedMem.size !== 1 && ArgGenObj.SolvedMem.Offset === undefined) { 151 | throw new Error(Program.Context.formatError(CurrentNode.Token.line, 152 | ' Overflow in argument size.')) 153 | } 154 | processedArgs.push(ArgGenObj.SolvedMem) 155 | }) 156 | if (type === 'api') { 157 | returnAssemblyCode += createAPICallInstruction( 158 | Program, 159 | utils.genAPICallToken(CurrentNode.Token.line, ifnToCall.asmName), 160 | FnRetObj, 161 | processedArgs 162 | ) 163 | } else { 164 | if (ifnToCall.name === 'bcftol' || ifnToCall.name === 'bcltof') { 165 | utils.setMemoryDeclaration(processedArgs[0], ifnToCall.declaration) 166 | return { SolvedMem: processedArgs[0], asmCode: returnAssemblyCode } 167 | } 168 | returnAssemblyCode += createBuiltinInstruction( 169 | Program, 170 | utils.genBuiltInToken(CurrentNode.Token.line, ifnToCall.asmName), 171 | ifnToCall.builtin, 172 | FnRetObj, 173 | processedArgs 174 | ) 175 | } 176 | processedArgs.forEach(varnm => Program.Context.freeRegister(varnm.address)) 177 | return { SolvedMem: FnRetObj, asmCode: returnAssemblyCode } 178 | } 179 | return functionSolverMain() 180 | } 181 | -------------------------------------------------------------------------------- /src/typings/syntaxTypes.ts: -------------------------------------------------------------------------------- 1 | export type HEX_CONTENT = number | bigint | string 2 | 3 | export type CONSTANT_CONTENT = { 4 | value: HEX_CONTENT 5 | declaration: 'long' | 'fixed' 6 | } 7 | 8 | /** Allowed token types. 'PreToken' type only in first phase (not recursive). */ 9 | export type TOKEN_TYPES = 'Variable' | 'Constant' | 'Operator' | 'UnaryOperator' | 10 | 'SetUnaryOperator' | 'Assignment'| 'SetOperator'|'Comparision'|'CheckOperator'| 11 | 'Arr'|'CodeCave'|'CodeDomain'|'Delimiter'|'Terminator'|'Macro'|'Member'|'Colon'| 12 | 'Keyword'|'Function' | 'APICall' | 'BuiltInCall' | 'Push' | 'PreToken' 13 | 14 | export type DECLARATION_TYPES = 'void' | 'long' | 'fixed' | 'struct' | 'void_ptr' | 'long_ptr' | 'fixed_ptr' | 'struct_ptr' | '' 15 | 16 | export interface PRE_TOKEN { 17 | /** Line follows the scheme 'line:column' or '0:0' if unknown */ 18 | line: string 19 | precedence: number 20 | type: TOKEN_TYPES 21 | /** Empty string for Arr, CodeCave, CodeDomain */ 22 | value: string 23 | /** Only applicable to types: asm, break, continue, constant, struct or label */ 24 | extValue?: string 25 | } 26 | 27 | export interface TOKEN extends PRE_TOKEN { 28 | declaration?: DECLARATION_TYPES 29 | /** Only applicable to Arr, CodeCave, CodeDomain, Variable with modifier */ 30 | params?: TOKEN[] 31 | } 32 | 33 | export type MEMORY_BASE_TYPES = 'register' | 'long' | 'fixed' | 'constant' | 'struct' | 'structRef' | 'array' | 'label' | 'void' 34 | 35 | /** If constant, it is the number to shift. If variable, it is the address containing the value to shift. 36 | * Stores information about variable it is pointing to. 37 | */ 38 | export type OFFSET_MODIFIER_CONSTANT = { 39 | type: 'constant', 40 | value: number, 41 | declaration: DECLARATION_TYPES, 42 | typeDefinition?: string 43 | } 44 | export type OFFSET_MODIFIER_VARIABLE = { 45 | type: 'variable', 46 | addr: number, 47 | declaration: DECLARATION_TYPES, 48 | typeDefinition?: string 49 | } 50 | export type OFFSET_MODIFIER = OFFSET_MODIFIER_CONSTANT | OFFSET_MODIFIER_VARIABLE 51 | 52 | export type MEMORY_SLOT = { 53 | /** Variable base types: 'register' | 'long' | 'constant' | 'struct' | 'structRef' | 'array' | 'label' | 'void' */ 54 | type: MEMORY_BASE_TYPES 55 | /** Variable name in assembly code */ 56 | asmName: string 57 | /** Controls if variable was already defined an can be used. */ 58 | isDeclared: boolean 59 | /** Variable type during declaration */ 60 | declaration: DECLARATION_TYPES 61 | /** Control warning if using variables before setting it. */ 62 | isSet: boolean 63 | /** Control if a specific register to be used in this variable */ 64 | toBeRegister: boolean 65 | /** Offset in memory. -1 if this slot is not in memory */ 66 | address: number 67 | /** Variable name */ 68 | name: string 69 | /** Variable scope */ 70 | scope: string 71 | /** Line of declaration */ 72 | line: string 73 | /** Variable size in longs */ 74 | size: number 75 | /** struct type definition OR array type definition */ 76 | typeDefinition?: string 77 | /** For constants: content */ 78 | hexContent?: string 79 | /** Info about items of array. */ 80 | ArrayItem?: { 81 | /** item base type */ 82 | type: MEMORY_BASE_TYPES, 83 | /** item base declaration */ 84 | declaration: DECLARATION_TYPES, 85 | /** Item type definion (for structs) */ 86 | typeDefinition?: string, 87 | /** Item total size */ 88 | totalSize: number 89 | } 90 | /** Indicates to apply a shift to this memory address. Value must be deferenced to be evaluated. */ 91 | Offset?: OFFSET_MODIFIER 92 | } 93 | 94 | // eslint-disable-next-line no-use-before-define 95 | export type AST = UNARY_ASN | BINARY_ASN | NULL_ASN | END_ASN | LOOKUP_ASN | EXCEPTION_ASN | SWITCH_ASN 96 | 97 | export type UNARY_ASN = { 98 | /** Unary Abstract Syntax Node */ 99 | type: 'unaryASN' 100 | /** Unary operator token */ 101 | Operation: TOKEN 102 | /** Continuation of AST */ 103 | Center: AST 104 | } 105 | export type BINARY_ASN = { 106 | /** Binary Abstract Syntax Node */ 107 | type: 'binaryASN' 108 | /** Binary operator token */ 109 | Operation: TOKEN 110 | /** Left side AST */ 111 | Left: AST 112 | /** Right side AST */ 113 | Right: AST 114 | } 115 | 116 | export type NULL_ASN = { 117 | /** End Abstract Syntax Node */ 118 | type: 'nullASN' 119 | } 120 | 121 | export type END_ASN = { 122 | /** End Abstract Syntax Node */ 123 | type: 'endASN' 124 | /** End token. May be undefined, but most of times this situation leads to error. */ 125 | Token: TOKEN 126 | } 127 | export type TOKEN_MODIFIER_ARRAY = {type: 'Array', Center: AST} 128 | export type TOKEN_MODIFIER_MEMBER = {type: 'MemberByVal'|'MemberByRef', Center: TOKEN} 129 | export type TOKEN_MODIFIER = TOKEN_MODIFIER_ARRAY | TOKEN_MODIFIER_MEMBER 130 | 131 | export type LOOKUP_ASN = { 132 | /** Abstract Syntax Node for variables with modifiers to be evaluated in chain */ 133 | type: 'lookupASN' 134 | /** End token with type == 'Variable' or 'Function' */ 135 | Token: TOKEN 136 | /** Function arguments AST */ 137 | FunctionArgs?: AST 138 | /** Value modifiers like Arr or Members */ 139 | modifiers: TOKEN_MODIFIER[] 140 | } 141 | export type EXCEPTION_ASN = { 142 | /** exception Abstract Syntax Node. Used for SetUnaryOperator */ 143 | type: 'exceptionASN' 144 | /** Binary operator token. Currently only SetUnaryOperator */ 145 | Operation: TOKEN 146 | /** Left side AST. Indicating pre-increment or pre-decrement */ 147 | Left?: AST 148 | /** Rigth side AST. Indicating post-increment or post-decrement */ 149 | Right?: AST 150 | } 151 | export type SWITCH_ASN = { 152 | /** Abstract Syntax Node for switch statements that will be evaluated for jump table. */ 153 | type: 'switchASN' 154 | /** switch expression part */ 155 | Expression: AST 156 | /** One item for each case statement in switch block */ 157 | caseConditions: AST[] 158 | } 159 | 160 | // eslint-disable-next-line no-use-before-define 161 | export type SENTENCES = SENTENCE_PHRASE | SENTENCE_IF_ENDIF | SENTENCE_IF_ELSE | SENTENCE_WHILE | SENTENCE_DO | SENTENCE_FOR | SENTENCE_STRUCT | SENTENCE_SWITCH | SENTENCE_CASE | SENTENCE_DEFAULT | SENTENCE_LABEL | SENTENCE_SCOPE 162 | export type SENTENCE_PHRASE = { 163 | type: 'phrase' 164 | /** phrase starting location ('line:column' scheme) */ 165 | line: string 166 | /** Array of tokens, recursive on Arr, Codecave and CodeDomain */ 167 | code?: TOKEN[] 168 | /** Tokens organized in an AST */ 169 | CodeAST?: AST 170 | } 171 | export type SENTENCE_IF_ENDIF = { 172 | type: 'ifEndif' 173 | id: string 174 | line: string 175 | condition?: TOKEN[] 176 | /** Tokens organized in an AST */ 177 | ConditionAST?: AST 178 | trueBlock: SENTENCES[] 179 | } 180 | export type SENTENCE_IF_ELSE = { 181 | type: 'ifElse' 182 | id: string 183 | line: string 184 | condition?: TOKEN[] 185 | ConditionAST?: AST 186 | trueBlock: SENTENCES[] 187 | falseBlock: SENTENCES[] 188 | } 189 | export type SENTENCE_WHILE = { 190 | type: 'while' 191 | id: string 192 | line: string 193 | condition?: TOKEN[] 194 | ConditionAST?: AST 195 | trueBlock: SENTENCES[] 196 | } 197 | export type SENTENCE_DO = { 198 | type: 'do' 199 | id: string 200 | line: string 201 | condition?: TOKEN[] 202 | ConditionAST?: AST 203 | trueBlock: SENTENCES[] 204 | } 205 | export type SENTENCE_FOR = { 206 | type: 'for' 207 | id: string 208 | line: string 209 | threeSentences: SENTENCE_PHRASE[] 210 | trueBlock: SENTENCES[] 211 | } 212 | export type SENTENCE_STRUCT = { 213 | type: 'struct', 214 | line: string, 215 | name: string, 216 | members: SENTENCES[], 217 | Phrase: SENTENCE_PHRASE 218 | } 219 | export type SENTENCE_CASE = { 220 | type: 'case' 221 | line: string 222 | caseId: string 223 | condition?: TOKEN[] 224 | } 225 | export type SENTENCE_DEFAULT = { 226 | type: 'default' 227 | line: string 228 | } 229 | export type SENTENCE_SWITCH = { 230 | type: 'switch' 231 | line: string 232 | expression?: TOKEN[] 233 | cases?: TOKEN[][] 234 | hasDefault: boolean 235 | block: SENTENCES[] 236 | JumpTable?: SWITCH_ASN 237 | } 238 | export type SENTENCE_LABEL = { 239 | type: 'label' 240 | line: string 241 | id: string 242 | } 243 | export type SENTENCE_SCOPE = { 244 | type: 'scope' 245 | id: string 246 | line: string 247 | ConditionAST?: AST 248 | alwaysBlock: SENTENCES[] 249 | } 250 | 251 | export type STRUCT_TYPE_DEFINITION = { 252 | type: 'struct', 253 | name: string, 254 | structMembers: MEMORY_SLOT[], 255 | structAccumulatedSize: [string, number][], 256 | MemoryTemplate: MEMORY_SLOT 257 | } 258 | export type ARRAY_TYPE_DEFINITION = { 259 | type: 'array' 260 | name: string 261 | arrayDimensions: number[] 262 | arrayMultiplierDim: number[] 263 | MemoryTemplate: MEMORY_SLOT 264 | } 265 | export type REGISTER_TYPE_DEFINITION = { 266 | type: 'register' 267 | name: '', 268 | MemoryTemplate: MEMORY_SLOT 269 | } 270 | export type LONG_TYPE_DEFINITION = { 271 | type: 'long' 272 | name: '', 273 | MemoryTemplate: MEMORY_SLOT 274 | } 275 | export type FIXED_TYPE_DEFINITION = { 276 | type: 'fixed' 277 | name: '', 278 | MemoryTemplate: MEMORY_SLOT 279 | } 280 | export type TYPE_DEFINITIONS = STRUCT_TYPE_DEFINITION | ARRAY_TYPE_DEFINITION | REGISTER_TYPE_DEFINITION | LONG_TYPE_DEFINITION | FIXED_TYPE_DEFINITION 281 | 282 | export type BUILTIN_TYPES = 'loop'|'receive'|'send'|'blockchain'|'contract'|'maps'|'fourArgsPlus'|'assets'|'special'|'internal' 283 | -------------------------------------------------------------------------------- /src/repository/__tests__/repository.spec.ts: -------------------------------------------------------------------------------- 1 | import { stringToHexstring, ReedSalomonAddressDecode, assertNotUndefined, assertNotEqual, assertExpression, deepCopy, parseDecimalNumber } from '../repository' 2 | 3 | const dummyLine = '0:0' 4 | 5 | describe('Strings to hexstring', () => { 6 | it('should convert: simple string ( <= 0x7f)', () => { 7 | const str = 'Simple' 8 | const hexstring = '00 00 65 6c 70 6d 69 53' 9 | const result = stringToHexstring(str, dummyLine) 10 | expect(result).toBe(hexstring.replace(/ /g, '')) 11 | }) 12 | it('should convert: string sucessfully ( > 0x7f and < 0x800)', () => { 13 | const str = 'Até' 14 | const hexstring = '00 00 00 00 a9 c3 74 41' 15 | const result = stringToHexstring(str, dummyLine) 16 | expect(result).toBe(hexstring.replace(/ /g, '')) 17 | }) 18 | it('should convert: utf split between longs', () => { 19 | const str = 'aaaaaaéa' 20 | const hexstring = ' 00 00 00 00 00 00 00 61 a9 c3 61 61 61 61 61 61' 21 | const result = stringToHexstring(str, dummyLine) 22 | expect(result).toBe(hexstring.replace(/ /g, '')) 23 | }) 24 | it('should convert string sucessfully (chinese)', () => { 25 | const str = '中华人民共和国' 26 | const hexstring = '000000bd9be58c92e5b185e591b0e6babae48e8de5adb8e4' 27 | const result = stringToHexstring(str, dummyLine) 28 | expect(result).toBe(hexstring.replace(/ /g, '')) 29 | }) 30 | it('should convert string sucessfully (empty string)', () => { 31 | const str = '' 32 | const hexstring = '00 00 00 00 00 00 00 00' 33 | const result = stringToHexstring(str, dummyLine) 34 | expect(result).toBe(hexstring.replace(/ /g, '')) 35 | }) 36 | it('should convert string sucessfully (between 0xdfff and 0x10000 )', () => { 37 | const str = '#>' 38 | const hexstring = '00 00 9e bc ef 83 bc ef' 39 | const result = stringToHexstring(str, dummyLine) 40 | expect(result).toBe(hexstring.replace(/ /g, '')) 41 | }) 42 | it('should convert string sucessfully (over 0x10000)', () => { 43 | const str = '🨁' 44 | const hexstring = '00 00 00 00 81 a8 9f f0' 45 | const result = stringToHexstring(str, dummyLine) 46 | expect(result).toBe(hexstring.replace(/ /g, '')) 47 | }) 48 | test('should throw: wrong string data', () => { 49 | expect(() => { 50 | const str = '🨁🨁🨁'.substr(0, 5) 51 | stringToHexstring(str, dummyLine) 52 | }).toThrowError(/^At line/) 53 | }) 54 | test('should throw: wrong string data', () => { 55 | expect(() => { 56 | const str = '🨁🨁🨁'.substr(0, 5) + 'SS' 57 | stringToHexstring(str, dummyLine) 58 | }).toThrowError(/^At line/) 59 | }) 60 | it('should convert: simple escape codes', () => { 61 | const str = '3\\n3\\r3\\t3\\\\3\\\'3\\"3' 62 | const hexstring = '00 00 00 33 22 33 27 33 5c 33 09 33 0d 33 0a 33' 63 | const result = stringToHexstring(str, dummyLine) 64 | expect(result).toBe(hexstring.replace(/ /g, '')) 65 | }) 66 | test('should throw: wrong simple escape code', () => { 67 | expect(() => { 68 | const str = '33\\c33' 69 | stringToHexstring(str, dummyLine) 70 | }).toThrowError(/^At line/) 71 | }) 72 | it('should convert: escaped hexadecimal right', () => { 73 | const str = 'a\\x01a' 74 | const hexstring = '00 00 00 00 00 61 01 61' 75 | const result = stringToHexstring(str, dummyLine) 76 | expect(result).toBe(hexstring.replace(/ /g, '')) 77 | }) 78 | test('should throw: wrong escaped hexadecimal', () => { 79 | expect(() => { 80 | const str = '33\\xfg33' 81 | stringToHexstring(str, dummyLine) 82 | }).toThrowError(/^At line/) 83 | }) 84 | test('should throw: wrong escaped hexadecimal', () => { 85 | expect(() => { 86 | const str = '33\\xf' 87 | stringToHexstring(str, dummyLine) 88 | }).toThrowError(/^At line/) 89 | }) 90 | it('should convert: escaped unicode right', () => { 91 | const str = 'a\\u668ba' 92 | const hexstring = '00 00 00 61 8b 9a e6 61' 93 | const result = stringToHexstring(str, dummyLine) 94 | expect(result).toBe(hexstring.replace(/ /g, '')) 95 | }) 96 | test('should throw: wrong escaped unicode', () => { 97 | expect(() => { 98 | const str = '33\\u3fgh33' 99 | stringToHexstring(str, dummyLine) 100 | }).toThrowError(/^At line/) 101 | }) 102 | test('should throw: wrong escaped unicode', () => { 103 | expect(() => { 104 | const str = '33\\u3f3' 105 | stringToHexstring(str, dummyLine) 106 | }).toThrowError(/^At line/) 107 | }) 108 | }) 109 | 110 | describe('Reed-Salomon decode', () => { 111 | it('should convert: EYCN-TQE9-K5RV-GQZFF', () => { 112 | const str = 'EYCN-TQE9-K5RV-GQZFF' 113 | const hexstring = 'e6 b7 f6 cd 98 76 79 54' 114 | const result = ReedSalomonAddressDecode(str, dummyLine) 115 | expect(result).toBe(hexstring.replace(/ /g, '')) 116 | }) 117 | it('should convert: 2222-2622-Y8GQ-22222', () => { 118 | const str = '2222-2622-Y8GQ-22222' 119 | const hexstring = '00 00 00 01 00 00 00 00' 120 | const result = ReedSalomonAddressDecode(str, dummyLine) 121 | expect(result).toBe(hexstring.replace(/ /g, '')) 122 | }) 123 | it('should convert: 2222-2222-2222-22222 (id zero)', () => { 124 | const str = '2222-2222-2222-22222' 125 | const hexstring = '00 00 00 00 00 00 00 00' 126 | const result = ReedSalomonAddressDecode(str, dummyLine) 127 | expect(result).toBe(hexstring.replace(/ /g, '')) 128 | }) 129 | it('should convert: ZZZZ-ZZZZ-QY2K-HZZZZ (last valid id)', () => { 130 | const str = 'ZZZZ-ZZZZ-QY2K-HZZZZ' 131 | const hexstring = 'ff ff ff ff ff ff ff ff' 132 | const result = ReedSalomonAddressDecode(str, dummyLine) 133 | expect(result).toBe(hexstring.replace(/ /g, '')) 134 | }) 135 | test('should throw: Address overflow (id >= 2^64)', () => { 136 | expect(() => { 137 | const code = '2223-2222-AUZT-J2222' 138 | ReedSalomonAddressDecode(code, dummyLine) 139 | }).toThrowError(/^At line/) 140 | }) 141 | test('should throw: wrong address', () => { 142 | expect(() => { 143 | const code = 'LQSJ-DXPH-8HZG-CZXQC' 144 | ReedSalomonAddressDecode(code, dummyLine) 145 | }).toThrowError(/^At line/) 146 | }) 147 | test('should throw: wrong address', () => { 148 | expect(() => { 149 | const code = 'LQSJ-DXPH-HHZG-CZXQH' 150 | ReedSalomonAddressDecode(code, dummyLine) 151 | }).toThrowError(/^At line/) 152 | }) 153 | }) 154 | 155 | describe('assert/deepcopy functions', () => { 156 | it('should pass: assertNotUndefined', () => { 157 | const num = 2 158 | const result = assertNotUndefined(num + 2, 'New Error') 159 | expect(result).toBe(4) 160 | }) 161 | test('should throw: assertNotUndefined', () => { 162 | expect(() => { 163 | assertNotUndefined(undefined, 'New Error') 164 | }).toThrowError('New Error') 165 | }) 166 | test('should throw: assertNotUndefined', () => { 167 | expect(() => { 168 | assertNotUndefined(undefined) 169 | }).toThrowError('Internal error') 170 | }) 171 | it('should pass: assertNotEqual', () => { 172 | const num = 2 173 | const result = assertNotEqual(num + 2, 0, 'Error') 174 | expect(result).toBe(4) 175 | }) 176 | test('should throw: assertNotEqual', () => { 177 | expect(() => { 178 | assertNotEqual(undefined, 5, 'New Error') 179 | }).toThrowError('New Error') 180 | }) 181 | test('should throw: assertNotEqual', () => { 182 | expect(() => { 183 | assertNotEqual(2, 2, 'Internal error') 184 | }).toThrowError('Internal error') 185 | }) 186 | it('should pass: assertExpression', () => { 187 | let num = 2 188 | num++ 189 | const result = assertExpression(num === 3, 'Error') 190 | expect(result).toBe(true) 191 | }) 192 | test('should throw: assertExpression', () => { 193 | let num = 2 194 | num++ 195 | expect(() => { 196 | assertExpression(num === 0, 'New Error') 197 | }).toThrowError('New Error') 198 | }) 199 | test('should throw: assertExpression', () => { 200 | let num = 2 201 | num++ 202 | expect(() => { 203 | assertExpression(num === 0) 204 | }).toThrowError('Internal error') 205 | }) 206 | test('should throw: assertExpression', () => { 207 | let num = 2 208 | num++ 209 | expect(() => { 210 | assertExpression(num === 0) 211 | }).toThrowError('Internal error') 212 | }) 213 | it('should pass: deepCopy object', () => { 214 | const data = { a: 5, b: 'notjo' } 215 | const copy = deepCopy(data) 216 | expect(data).toEqual(copy) 217 | expect(data).not.toBe(copy) 218 | }) 219 | it('should pass: deepCopy array', () => { 220 | const data = [5, 23, 'notjo'] 221 | const copy = deepCopy(data) 222 | expect(copy).toBeInstanceOf(Array) 223 | expect(data).toEqual(copy) 224 | expect(data).not.toBe(copy) 225 | }) 226 | it('should pass: deepCopy date', () => { 227 | const data = new Date(2018, 11, 24, 10, 33, 30, 0) 228 | const copy = deepCopy(data) 229 | expect(copy).toBeInstanceOf(Date) 230 | expect(data).toEqual(copy) 231 | expect(data).not.toBe(copy) 232 | }) 233 | }) 234 | 235 | describe('parseDecimal error', () => { 236 | test('should throw: two decimal points', () => { 237 | expect(() => { 238 | parseDecimalNumber('2.234.33', dummyLine) 239 | }).toThrowError(/^At line/) 240 | }) 241 | test('should throw: more than 8 decimals', () => { 242 | expect(() => { 243 | parseDecimalNumber('2.123456789', dummyLine) 244 | }).toThrowError(/^At line/) 245 | }) 246 | }) 247 | --------------------------------------------------------------------------------