├── _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 | [](https://sonarcloud.io/dashboard?id=deleterium_SmartC)
5 | [](https://sonarcloud.io/dashboard?id=deleterium_SmartC)
6 | [](https://sonarcloud.io/dashboard?id=deleterium_SmartC)
7 | [](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 |
102 | Use this page to debug or inspect SmartC process, using a browser.
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
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 |
--------------------------------------------------------------------------------