Coder Social home page Coder Social logo

tealish's Introduction

Tealish, A readable language for Algorand, Powered by Tinyman


Tealish is a readable language for the Algorand Virtual Machine. It enables developers to write TEAL in a procedural style optimized for readability.

Tealish transpiles to Teal rather than compiling directly to AVM bytecode. The produced Teal is as close to handwritten idiomatic Teal as possible. The original source Tealish (including comments) is included as comments in the generated Teal. The generated Teal is intended to be readable and auditable. The generated Teal should not be surprising - the Tealish writer should be able to easily imagine the generated Teal.

Tealish is not a general purpose programming language. It is designed specifically for writing contracts for the AVM, optimizing for common patterns.

Status

Tealish has been used to write large production contracts but it is not currently considered Production Ready for general use. It may have unexpected behavior outside of the scenarios it has been used for until now.

Docs

https://tealish.readthedocs.io/

Installation

pip install tealish

Minimal Example

A simple example demonstrating assertions, state, if statements and inner transactions:

#pragma version 6

if Txn.OnCompletion == UpdateApplication:
    assert(Txn.Sender == Global.CreatorAddress)
    exit(1)
end

assert(Txn.OnCompletion == NoOp)

int counter = app_global_get("counter")
counter = counter + 1
app_global_put("counter", counter)

if counter == 10:
    inner_txn:
        TypeEnum: Pay
        Receiver: Txn.Sender
        Amount: 10000000
    end
elif counter > 10:
    exit(0)
end

exit(1)

Compiling

    tealish compile examples/minimal_example/counter_prize.tl

This will produce counter_prize.teal in the build subdirectory.

Editor Support

A VS Code extension for syntax highlighting of Tealish & TEAL is available here

Starter Template

#pragma version 8

if Txn.ApplicationID == 0:
    # Handle Create App
    exit(1)
end

router:
    method_a
    method_b
    method_c
    update_app
    delete_app
end

@public(OnCompletion=UpdateApplication)
func update_app():
    # Handle Update App
    # Example: Only allow the Creator to update the app (useful during development)
    assert(Txn.Sender == Global.CreatorAddress)
    # OR Disallow Update App by removing this function
    return
end

@public(OnCompletion=DeleteApplication)
func delete_app():
    # Handle Delete App
    # Example: Only allow the Creator to update the app (useful during development)
    assert(Txn.Sender == Global.CreatorAddress)
    # OR Disallow Delete App by removing this function
    return
end

@public()
func method_a(user_address: bytes[32], amount: int):
    # Handle method_a
    # some statements here
    return
end

@public()
func method_b():
    # Handle method_b
    # some statements here
    return
end

@public()
func method_c():
    # Handle method_c
    # some statements here
    return
end

Design Goals

Tealish is designed first and foremost to be a more readable version of Teal. The biggest difference between Teal and Tealish is the stack is made implicit in Tealish instead of being explicit as in Teal.

Readability is achieved by the following:

  • Multiple operations on a single line
  • Semantic names for scratch slots (variables)
  • Aliases for values on stack
  • Named constants
  • High level language concepts (if/elif/else, loops, switches, structs, functions, function router)
  • A simple style convention

Safety Features:

  • Readability
  • Named scratch slots
  • Scoped scratch slots
  • Type checking

Any Teal opcode can be used in Tealish in a procedural style. Additionally there is syntactic sugar for some common operations. When explicit stack manipulation is required raw Teal can be used inline within a Tealish program.

Tealish is a procedural language, executed from top to bottom. Statements can exist inside blocks or at the top level. The first statement of a program is the entry point of the program. The program can exit on any line. Execution can branch from one block to another using jump or switch statements.

Blocks are used to define scopes. Variables, Functions and Blocks are scoped to the block they are defined in and are available to any nested blocks.

Blocks are not functions:

  • they do not take arguments
  • they do not have independent stack space
  • they are not re-entrant

Functions are used to define reusable pieces of functionality. They can take arguments and return values. They can read variables from the scope in which they are defined but they may not assign to variables outside their local scope. Functions may have side effects through the use of state manipulation or inner transactions.

tealish's People

Contributors

aorumbayev avatar barnjamin avatar edizcelik avatar etzellux avatar fergalwalsh avatar gokselcoban avatar obiajulum avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tealish's Issues

[BUG] cannot parse expression with `const int`

Describe the bug

int x = 1
const int X = 1
int y = 1
 # next line is ok
_ = x * y
 # next line is NOT ok
_ = X * y

Error: Cannot parse "X * y" as Expression

To Reproduce
Steps to reproduce the behavior.

Expected behavior
both lines should compile

Additional context
Add any other context about the problem here.

Refactor Statement and Expression node parent classes

Since Statements and Expressions both inherit from BaseNode some issues arise in accessing attributes that are not defined in one or the other. We can change the inheritance structure to split the different Node types for better type information and prevent issues with inferring attribute availability.

See this comment for more: #27 (review)

Roadmap

Please see the Tealish Roadmap #64 post in Discussions for the current project roadmap.

Inner Transactions: Comment support

The inner transaction block doesn't support comments.

Problem:

inner_txn:
    # This is a comment
    TypeEnum: Axfer
    ...
end

Possible workaround:

# This is a comment
inner_txn:
    TypeEnum: Axfer
    ...
end

ABI support

Just wondering if ABI not being supported (as stated in FAQ explicitly) is something that is planned for long term (giving more 'flexibility' in some sense) so people deal with abi compliant methods themselves or its just that tealish was developed (started development) before ABI was introduced?

Would be an icing completing the cake if it gets added to tealish as well ;)

Macro support

algorand/go-algorand#4737 is introducing support for macros in TEAL.

One useful thing to do with macros is use them to define the constants Tealish supports.

I believe the following patch will enable that:

diff --git a/tealish/expression_nodes.py b/tealish/expression_nodes.py
index 4b365ac..409daca 100644
--- a/tealish/expression_nodes.py
+++ b/tealish/expression_nodes.py
@@ -78,9 +78,9 @@ class Constant(BaseNode):
 
     def write_teal(self, writer):
         if self.type == "int":
-            writer.write(self, f"pushint {self.value} // {self.name}")
+            writer.write(self, f"pushint {self.name} // {self.value}")
         elif self.type == "bytes":
-            writer.write(self, f"pushbytes {self.value} // {self.name}")
+            writer.write(self, f"pushbytes {self.name} // {self.value}")
 
     def _tealish(self, formatter=None):
         return f"{self.name}"
diff --git a/tealish/nodes.py b/tealish/nodes.py
index 62f9ce1..d5a53d9 100644
--- a/tealish/nodes.py
+++ b/tealish/nodes.py
@@ -334,6 +334,7 @@ class Const(LineStatement):
         scope["consts"][self.name] = [self.type, self.expression.value]
 
     def write_teal(self, writer):
+        writer.write (self, f"#define {self.name} {self.expression.value}")
         pass
 
     def _tealish(self, formatter=None):

this will result in tealish that goes from

#pragma version 8

struct Item:
    id: int
    foo: int
    name: bytes[10]
end

const int KNOWN_ID = 64738
const bytes BOX_NAME = "tealishbox"
const bytes KNOWN_ITEM = "fearghal"


box<Item> item1 = CreateBox(BOX_NAME)
item1.id = KNOWN_ID
item1.foo = KNOWN_ID + 1
item1.name = KNOWN_ITEM

log(itob(item1.id))
log(itob(item1.foo))
log(item1.name)

to TEAL

#pragma version 8


#define KNOWN_ID 64738
#define BOX_NAME "tealishbox"
#define KNOWN_ITEM "fearghal"


// box<Item> item1 = CreateBox(BOX_NAME) [slot 0]
pushbytes BOX_NAME // "tealishbox"
dup
pushint 26
box_create
assert // assert created
store 0 // item1
// item1.id = KNOWN_ID [box]
load 0 // box key item1
pushint 0 // offset
pushint KNOWN_ID // 64738
itob
box_replace // item1.id
// item1.foo = KNOWN_ID + 1 [box]
load 0 // box key item1
pushint 8 // offset
pushint KNOWN_ID // 64738
pushint 1
+
itob
box_replace // item1.foo
// item1.name = KNOWN_ITEM [box]
load 0 // box key item1
pushint 16 // offset
pushbytes KNOWN_ITEM // "fearghal"
box_replace // item1.name

// log(itob(item1.id))
load 0 // box key item1
pushint 0 // offset
pushint 8 // size
box_extract // item1.id
btoi
itob
log
// log(itob(item1.foo))
load 0 // box key item1
pushint 8 // offset
pushint 8 // size
box_extract // item1.foo
btoi
itob
log
// log(item1.name)
load 0 // box key item1
pushint 16 // offset
pushint 10 // size
box_extract // item1.name
log

Struct typing class definition

The structs dictionary provides little information about the types it holds.

A new class should be created to provide information about a given Struct type.

See this comment for more: #27 (comment)

[BUG] Multiple struct with same field names cause wrong TEAL (confused) code

Describe the bug
Multiple struct with same field names cause wrong TEAL (confused) code

struct StructA:
 fieldname: int
 q: int
end

struct StructB:
 r: int
 fieldname: int
end

block main:
    StructA a = itob(1)
    int x = a.fieldname
end

results in

// block main
main:
  // StructA a = itob(1) [slot 0]
  pushint 1
  itob
  store 0 // a
  // int x = a.fieldname [slot 1]
  load 0 // a
  pushint 8
  extract_uint64 // fieldname
  store 1 // x

because fieldname is in a different place in the different structs

(the pushint 8 is wrong in the TEAL code, should be pushint 0 since fieldname is the first field in StructA

To Reproduce
code above

Expected behavior
perhaps field names need the struct name as a prefix to differentiate

Additional context
Add any other context about the problem here.

Tealish dont't compile don't give any error just making stuck on terminal with reasigned variable and validated as conditional

Describe the bug
With this code the compiler don't compiles the compiled get stuck.

https://gist.github.com/helderjnpinto/2416f4d5e3f0dc3349ee07d976e15536

To Reproduce
Tealish compile Test.tl

Expected behavior
code compile

Additional context
I think the problem here is in the if statement if returns (line 72 ) something compiles and if is removed compiles as well

https://gist.github.com/helderjnpinto/2416f4d5e3f0dc3349ee07d976e15536#file-test-bug-tealish-L71-L73

with this case compiles:

func calc(from_round: int) int:
    int end_round = Global.Round
    int test = 1
    if end_round > 10:
       test = 10
    end

    return end_round - from_round
end

Setting the value of a field of a structured box can overflow onto the next field

Describe the bug
{box_name}.{field_name} = {value}. replaces the field {field_name} of box {box_name} created from a struct without considering the size of {value}. This issue tracks the bug when setting the value of a field to a byte array that is larger than the size defined overflowing into the next field.

To Reproduce

#pragma version 8

struct UserInfo:
    birthday: bytes[10]
    favourite_colour: bytes[20]
end

if Txn.ApplicationID == 0:
    exit(1)
end

box<UserInfo> info_box = CreateBox("foo")
info_box.birthday = "06/02/2023OVERFLOW"
exit(1)

Expected behavior
Field favourite_colour of box with key "foo" should have an empty value ("") but is instead "OVERFLOW". You could also argue that info_box.birthday = "06/02/2023OVERFLOW" should fail outright (assigning a value larger than the size defined in the struct).

Additional context
Related issue: #74

[BUG]

Describe the bug
Order of return vars in code and docs mixed up

To Reproduce
e.g.
box_get(A: bytes) โ†’ bytes, int in https://tealish.tinyman.org/en/latest/avm.html
but in code:

int, bytes = box_get(key)

Expected behavior
box_get(A: bytes) โ†’ int, bytes in docs
xor

bytes, int = box_get(key)

in code

Additional context
this is true for some other opcodes as well

State Schemas

Tealish could allow the developer to define a schema for Global & Local states. The schema would serve two purposes:

  • indicate the number of int & byte slices used by the app
  • naming & typing of state keys

The schema should also allow for dynamically named state fields.

The syntax below is just an idea at this stage

Static state keys could be accessed using a field lookup syntax just like structs & Boxes. e.g

assert(Txn.Sender == GlobalState.manager_address)

Local state keys could use something like this:

assert(Txn.Sender == LocalState[account].manager_address)

When dynamic keys are required the existing opcodes (app_global_get & app_local_get ) should be used unless a good argument can be made for having additional syntactic sugar for these.

The schemas would be queryable from the Tealish language model of the program to allow other tools to export/consume them.

Needs more fleshing out before implementation.

@barnjamin, do correct me if I have mangled your idea :) And please add any further detail you have in mind at this stage.

Comments inside Switch break parser

image

Compiling auction.tl to build/auction.teal
Error: Pattern ((?P<expression>.*): (?P<block_name>.*)) does not match for SwitchOption for line "# test"

Add support for include or import

I have many helper/utility functions that are shared between multiple related contracts. Whether its shared constants, or common helper functions (like beaker/pyteal utils functions), Tealish currently forces everything to be in a single file.

This is forcing copy-paste duplication of large chunks of code in some cases.

A completely fleshed out import mechanism isn't particularly necessary, but a simple C like #include that injected from other files would address this problem well enough.

for loop direction

I noticed that for loops always expect to increment between the start and end values, only breaking when var equals exactly the end value (which could be a problem if it somehow skips over it). However if you wanted to loop from a higher value to a lower value (e.g. swap the order of for n in 1:5: to for n in 5:1:) it results in TEAL that will hit an AVM error when evaluated ("cost budget exceeded"), since it never breaks.

At first I thought maybe I could quickly modify the tealish/__init__.py file to check if start was greater than end, then write "-" instead of "+"... And then I realised they don't have to be defined values at all, and may be unkonwn until runtime.

int x = btoi(Txn.ApplicationArgs[0])
int y = btoi(Txn.ApplicationArgs[1])
for n in x:y:
  pop(n)
end

Should there be an easy way to define or flip the default direction of a for loop without having to write additional logic to work with an incrementing value?

Comments should be allowed anywhere in the code, including after lines.

Currently, # comments are only allowed on a line by themself.

They should be allowed after lines as well:
const int MY_CONST = 42 # Magic constant

Variations of this same issue are also referenced in #45 and #15

Comments should also be allowed before the #pragma version XX line (ie: templated copyright lines for eg)

[BUG] Tealish format error

Describe the bug
Using tealish format converts the following code in wrong way

To Reproduce

bytes frc_proofs = Txn.ApplicationArgs[4]
bytes rewards_proof = extract((index * 32), 32, frc_proofs)

with something with this run
tealish format approval.tl

and outputs this

bytes frc_proofs = Txn.ApplicationArgs[4]
bytes rewards_proof = extract(<tealish.expression_nodes.Group object at 0x7f8a4e7ec790> 32, frc_proofs)

Expected behavior
Just format the code not change the code

Need 'template' support similar to PyTeal

My infra works with raw teal, so I use beaker, pyteal, etc. to generate TEAL that's checked-in and built directly into binaries or part of a bootstrapping process.
That raw teal includes 'template' substitutions that are replaced with values specific to each environment (testnet / mainnet contract IDs, or parameter values for eg).

In PyTeal, a reference to a templated int (same w/ Bytes) might be like:
xxx.store( Itob(Tmpl.Int("TMPL_XXX")) )
which might become:

int TMPL_XXXX
itob
store 50

in TEAL. My code converts that prior to compilation, replacing the templates in descending length order.

This is one of the showstoppers for me in using Tealish.

Provide better error message (or more specific to situation) for evaluation order issues.

A statement like:
if a == 2 && b ==3:
will yield an error like:
Error: Cannot parse "if a == 2 && b ==3" as Expression at line 100

I had a more involved if statement in my particular case but I fought for quite a bit of time trying to figure out what was wrong (and there's no examples in the repo of an && expression like above) until I realized it wanted
if (a==2) && (b==3):

Tealish should make it extremely clear that the evaluation order isn't defined and needs to be. The error instead reads almost like a generic 'something's wrong' - ie: a syntax error.

Use frame pointer opcodes for functions - part 1

AVM 8+ has support for frame pointers which allow for more efficient stack & slot usage for subroutines (functions in Tealish).

Tealish can generate Teal that uses proto at the start of function definitions instead of stores. frame_dig can be used to access the function args from the stack without having to store them in slots.

Teal example for reference:

pushbytes "ab"
pushbytes "cd"
// log(myfunc("ab", "cd"))
callsub myfunc
log
pushint 1
return


// myfunc(x: bytes, y: bytes) bytes:
myfunc:
// specify that our function takes 2 arguments and has one return value
proto 2 1 // internally puts the first 2 values from the stack into the frame
frame_dig -2 // first arg
frame_dig -1 // second arg
concat
retsub

Replacing the value of a field of a structured box can keep parts of the replaced value

Describe the bug
{box_name}.{field_name} = {value}. replaces the field {field_name} of box {box_name} created from a struct without considering the size of {value}. This issue tracks the bug when parts of the replaced value remain after replacing it with a byte array of a smaller size than the current value.

To Reproduce

#pragma version 8

struct UserInfo:
    favourite_colour: bytes[20]
end

if Txn.ApplicationID == 0:
    exit(1)
end

box<UserInfo> info_box = CreateBox("foo")
info_box.favourite_colour = "yellow"
info_box.favourite_colour = "red"
exit(1)

Expected behavior
Field favourite_colour of box with key "foo" should have a value of "red" but is instead "redlow".

Additional context
Related issue: #75

Need support for alternative format Bytes values

PyTeal suports Bytes syntax variants like:

Bytes("base16", "0x052001018008010203040506")

I use this to construct TEAL bytcode that's manipulate for LSIG references inside a contract. I don't see any way to do this in Tealish at the moment.

ABI support

Is your feature request related to a problem? Please describe.
By the best practicies it is better to write teal code which supports ABI calls.

Describe the solution you'd like
Please implement ABI support to tealish.

Describe alternatives you've considered
All other pyteal compilers support abi. Pyteal, beaker, ...

Additional context
I like idea of tealish that you copy the comments to the teal or that you use the same opcode names as in teal, but it is quite crucial for new projects to use the abi. Soon there will be release of shared resources and this will drive adoption of single abi calls which might be more complex.

Scope type definition

Currently the scope on Nodes is a Dict[str, Any] but this doesn't provide much information about what it contains.

A new class should be defined to provide better type information for scope and the nodes should be updated to use it.

Stack type definition

Currently base types (int,bytes,any,none) are strings that we match against.

A new class that subclasses Enum type, and possibly also str would be helpful to prevent us from doing string comparison and provide better type hints in containing classes

See this comment for a bit more: #27 (comment)

Future features

Thank you so much, this project is amazing! Welcome to Costco, I love you. I didn't look really thoroughly but from a glance, I am really hoping to use just about ALL of the things you mentioned in FUTURE.md. It would be amazing if vim or VSCode could eventually have autocomplete for calling public methods in an external tealish contract (module) (maybe autogenerated app call wrappers from the contract JSON?).

Anyway, thanks for all the great work.

VS Code extension?

Is it possible to have this extension in the VS market place?
open source it?
Update readme to include the link instead of the dropbox link?

I just learned about tealish. This is very exciting. Nice work! Thank you.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.