stuff
This commit is contained in:
@@ -0,0 +1,77 @@
|
|||||||
|
|
||||||
|
-spec s2t_file(Signal) -> AstFile when
|
||||||
|
Signal :: [tk()],
|
||||||
|
AstFile :: #ns{meta :: file, kids :: asf()}.
|
||||||
|
|
||||||
|
s2t_file([]) ->
|
||||||
|
error(empty_file);
|
||||||
|
s2t_file(S0 = [#tk{pos = {_, FileCol}} | _]) ->
|
||||||
|
Blk0 = s2t_gulp_block(FileCol, S0),
|
||||||
|
Blk1 = t2t_parse_tds_in_block(Blk0),
|
||||||
|
#ns{meta = file, kids = [Blk1]}.
|
||||||
|
|
||||||
|
-spec s2t_gulp_block(BlkCol, Signal) -> Block when
|
||||||
|
BlkCol :: pos_integer(),
|
||||||
|
Signal :: [tk()],
|
||||||
|
Block :: #ns{meta :: block}.
|
||||||
|
|
||||||
|
s2t_gulp_block(BCol, Tks) ->
|
||||||
|
% sanity check
|
||||||
|
InBlock = fun(#tk{pos = {_, TCol}}) -> BCol =< TCol end,
|
||||||
|
true = lists:all(InBlock, Tks),
|
||||||
|
BlockItems = s2f_block_items(BCol, Tks),
|
||||||
|
#ns{meta = block, kids = BlockItems}.
|
||||||
|
|
||||||
|
-spec s2f_block_items(BCol, Signal) -> BlkItems when
|
||||||
|
BCol :: pos_integer(),
|
||||||
|
Signal :: [tk()],
|
||||||
|
BlkItems :: [BlkItem],
|
||||||
|
BlkItem :: #ns{meta :: block_item,
|
||||||
|
kids :: asf()}.
|
||||||
|
|
||||||
|
s2f_block_items(BCol, Signal) ->
|
||||||
|
s2f_block_items(BCol, [], Signal).
|
||||||
|
|
||||||
|
|
||||||
|
s2f_block_items(_BCol, Stk, []) ->
|
||||||
|
lists:reverse(Stk);
|
||||||
|
s2f_block_items(BCol, Stk, [#tk{pos = {_, BCol}} = T0 | F0]) ->
|
||||||
|
{slurp, BlkItem, F1} = s2t_slurp_block_item(BCol, T0, F0),
|
||||||
|
s2f_block_items(BCol, [BlkItem | Stk], F1).
|
||||||
|
|
||||||
|
|
||||||
|
s2t_slurp_block_item(BCol, T0, F0) ->
|
||||||
|
{ItemTokens, F1} = s2s_sw_block_item(BCol, T0, F0),
|
||||||
|
Item = #ns{meta = block_item, kids = ItemTokens},
|
||||||
|
{slurp, Item, F1}.
|
||||||
|
|
||||||
|
% sw = splitwith; kind of take/drop
|
||||||
|
s2s_sw_block_item(BCol, T0, F0) ->
|
||||||
|
InItem = fun(#tk{pos = {_, TCol}}) -> BCol < TCol end,
|
||||||
|
{F0_II, F1} = lists:splitwith(InItem, F0),
|
||||||
|
{[T0 | F0_II], F1}.
|
||||||
|
|
||||||
|
-spec t2t_parse_tds_in_block(Block0) -> Block1 when
|
||||||
|
Block0 :: ast(),
|
||||||
|
Block1 :: ast().
|
||||||
|
|
||||||
|
% go through and convert the block_item nodes to top
|
||||||
|
% decls
|
||||||
|
t2t_parse_tds_in_block(B0 = #ns{meta = block, kids = F0}) ->
|
||||||
|
F1 = lists:map(fun t2t_parse_td_from_item/1, F0),
|
||||||
|
B0#ns{kids = F1}.
|
||||||
|
|
||||||
|
|
||||||
|
-spec t2t_parse_td_from_item(BlockItem) -> TopDecl when
|
||||||
|
BlockItem :: #ns{meta :: block_item},
|
||||||
|
TopDecl :: #ns{meta :: td_meta()}.
|
||||||
|
|
||||||
|
t2t_parse_td_from_item(#ns{meta = block_item, kids = Signal}) ->
|
||||||
|
s2t_top_decl(Signal).
|
||||||
|
|
||||||
|
|
||||||
|
-spec s2t_top_decl(Signal) -> TdTree when
|
||||||
|
Signal :: [tk()],
|
||||||
|
TdTree :: ast().
|
||||||
|
|
||||||
|
s2t_top_decl(S0) ->
|
||||||
@@ -0,0 +1,292 @@
|
|||||||
|
# Syntax
|
||||||
|
|
||||||
|
## Lexical syntax
|
||||||
|
|
||||||
|
### Comments
|
||||||
|
|
||||||
|
Single line comments start with `//` and block comments are enclosed in `/*`
|
||||||
|
and `*/` and can be nested.
|
||||||
|
|
||||||
|
### Keywords
|
||||||
|
|
||||||
|
```
|
||||||
|
contract include let switch type record datatype if elif else function
|
||||||
|
stateful payable true false mod public entrypoint private indexed namespace
|
||||||
|
interface main using as for hiding
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tokens
|
||||||
|
|
||||||
|
- `Id = [a-z_][A-Za-z0-9_']*` identifiers start with a lower case letter.
|
||||||
|
- `Con = [A-Z][A-Za-z0-9_']*` constructors start with an upper case letter.
|
||||||
|
- `QId = (Con\.)+Id` qualified identifiers (e.g. `Map.member`)
|
||||||
|
- `QCon = (Con\.)+Con` qualified constructor
|
||||||
|
- `TVar = 'Id` type variable (e.g `'a`, `'b`)
|
||||||
|
- `Int = [0-9]+(_[0-9]+)*|0x[0-9A-Fa-f]+(_[0-9A-Fa-f]+)*` integer literal with optional `_` separators
|
||||||
|
- `Bytes = #[0-9A-Fa-f]+(_[0-9A-Fa-f]+)*` byte array literal with optional `_` separators
|
||||||
|
- `String` string literal enclosed in `"` with escape character `\`
|
||||||
|
- `Char` character literal enclosed in `'` with escape character `\`
|
||||||
|
- `AccountAddress` base58-encoded 32 byte account pubkey with `ak_` prefix
|
||||||
|
- `ContractAddress` base58-encoded 32 byte contract address with `ct_` prefix
|
||||||
|
- `Signature` base58-encoded 64 byte cryptographic signature with `sg_` prefix
|
||||||
|
|
||||||
|
Valid string escape codes are
|
||||||
|
|
||||||
|
| Escape | ASCII | |
|
||||||
|
|---------------|-------------|---|
|
||||||
|
| `\b` | 8 | |
|
||||||
|
| `\t` | 9 | |
|
||||||
|
| `\n` | 10 | |
|
||||||
|
| `\v` | 11 | |
|
||||||
|
| `\f` | 12 | |
|
||||||
|
| `\r` | 13 | |
|
||||||
|
| `\e` | 27 | |
|
||||||
|
| `\xHexDigits` | *HexDigits* | |
|
||||||
|
|
||||||
|
|
||||||
|
See the [identifier encoding scheme](https://git.qpq.swiss/QPQ-AG/protocol/src/branch/master/node/api/api_encoding.md) for the
|
||||||
|
details on the base58 literals.
|
||||||
|
|
||||||
|
## Layout blocks
|
||||||
|
|
||||||
|
Sophia uses Python-style layout rules to group declarations and statements. A
|
||||||
|
layout block with more than one element must start on a separate line and be
|
||||||
|
indented more than the currently enclosing layout block. Blocks with a single
|
||||||
|
element can be written on the same line as the previous token.
|
||||||
|
|
||||||
|
Each element of the block must share the same indentation and no part of an
|
||||||
|
element may be indented less than the indentation of the block. For instance
|
||||||
|
|
||||||
|
```sophia
|
||||||
|
contract Layout =
|
||||||
|
function foo() = 0 // no layout
|
||||||
|
function bar() = // layout block starts on next line
|
||||||
|
let x = foo() // indented more than 2 spaces
|
||||||
|
x
|
||||||
|
+ 1 // the '+' is indented more than the 'x'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notation
|
||||||
|
|
||||||
|
In describing the syntax below, we use the following conventions:
|
||||||
|
|
||||||
|
- Upper-case identifiers denote non-terminals (like `Expr`) or terminals with
|
||||||
|
some associated value (like `Id`).
|
||||||
|
- Keywords and symbols are enclosed in single quotes: `'let'` or `'='`.
|
||||||
|
- Choices are separated by vertical bars: `|`.
|
||||||
|
- Optional elements are enclosed in `[` square brackets `]`.
|
||||||
|
- `(` Parentheses `)` are used for grouping.
|
||||||
|
- Zero or more repetitions are denoted by a postfix `*`, and one or more
|
||||||
|
repetitions by a `+`.
|
||||||
|
- `Block(X)` denotes a layout block of `X`s.
|
||||||
|
- `Sep(X, S)` is short for `[X (S X)*]`, i.e. a possibly empty sequence of `X`s
|
||||||
|
separated by `S`s.
|
||||||
|
- `Sep1(X, S)` is short for `X (S X)*`, i.e. same as `Sep`, but must not be empty.
|
||||||
|
|
||||||
|
|
||||||
|
## Declarations
|
||||||
|
|
||||||
|
A Sophia file consists of a sequence of *declarations* in a layout block.
|
||||||
|
|
||||||
|
```c
|
||||||
|
File ::= Block(TopDecl)
|
||||||
|
|
||||||
|
TopDecl ::= ['payable'] ['main'] 'contract' Con [Implement] '=' Block(Decl)
|
||||||
|
| 'contract' 'interface' Con [Implement] '=' Block(Decl)
|
||||||
|
| 'namespace' Con '=' Block(Decl)
|
||||||
|
| '@compiler' PragmaOp Version
|
||||||
|
| 'include' String
|
||||||
|
| Using
|
||||||
|
|
||||||
|
Implement ::= ':' Sep1(Con, ',')
|
||||||
|
|
||||||
|
Decl ::= 'type' Id ['(' TVar* ')'] '=' TypeAlias
|
||||||
|
| 'record' Id ['(' TVar* ')'] '=' RecordType
|
||||||
|
| 'datatype' Id ['(' TVar* ')'] '=' DataType
|
||||||
|
| 'let' Id [':' Type] '=' Expr
|
||||||
|
| (EModifier* 'entrypoint' | FModifier* 'function') Block(FunDecl)
|
||||||
|
| Using
|
||||||
|
|
||||||
|
FunDecl ::= Id ':' Type // Type signature
|
||||||
|
| Id Args [':' Type] '=' Block(Stmt) // Definition
|
||||||
|
| Id Args [':' Type] Block(GuardedDef) // Guarded definitions
|
||||||
|
|
||||||
|
GuardedDef ::= '|' Sep1(Expr, ',') '=' Block(Stmt)
|
||||||
|
|
||||||
|
Using ::= 'using' Con ['as' Con] [UsingParts]
|
||||||
|
UsingParts ::= 'for' '[' Sep1(Id, ',') ']'
|
||||||
|
| 'hiding' '[' Sep1(Id, ',') ']'
|
||||||
|
|
||||||
|
PragmaOp ::= '<' | '=<' | '==' | '>=' | '>'
|
||||||
|
Version ::= Sep1(Int, '.')
|
||||||
|
|
||||||
|
EModifier ::= 'payable' | 'stateful'
|
||||||
|
FModifier ::= 'stateful' | 'private'
|
||||||
|
|
||||||
|
Args ::= '(' Sep(Pattern, ',') ')'
|
||||||
|
```
|
||||||
|
|
||||||
|
Contract declarations must appear at the top-level.
|
||||||
|
|
||||||
|
For example,
|
||||||
|
```sophia
|
||||||
|
contract Test =
|
||||||
|
type t = int
|
||||||
|
entrypoint add (x : t, y : t) = x + y
|
||||||
|
```
|
||||||
|
|
||||||
|
There are three forms of type declarations: type aliases (declared with the
|
||||||
|
`type` keyword), record type definitions (`record`) and data type definitions
|
||||||
|
(`datatype`):
|
||||||
|
|
||||||
|
```c
|
||||||
|
TypeAlias ::= Type
|
||||||
|
RecordType ::= '{' Sep(FieldType, ',') '}'
|
||||||
|
DataType ::= Sep1(ConDecl, '|')
|
||||||
|
|
||||||
|
FieldType ::= Id ':' Type
|
||||||
|
ConDecl ::= Con ['(' Sep1(Type, ',') ')']
|
||||||
|
```
|
||||||
|
|
||||||
|
For example,
|
||||||
|
```sophia
|
||||||
|
record point('a) = {x : 'a, y : 'a}
|
||||||
|
datatype shape('a) = Circle(point('a), 'a) | Rect(point('a), point('a))
|
||||||
|
type int_shape = shape(int)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Types
|
||||||
|
|
||||||
|
```c
|
||||||
|
Type ::= Domain '=>' Type // Function type
|
||||||
|
| Type '(' Sep(Type, ',') ')' // Type application
|
||||||
|
| '(' Type ')' // Parens
|
||||||
|
| 'unit' | Sep(Type, '*') // Tuples
|
||||||
|
| Id | QId | TVar
|
||||||
|
|
||||||
|
Domain ::= Type // Single argument
|
||||||
|
| '(' Sep(Type, ',') ')' // Multiple arguments
|
||||||
|
```
|
||||||
|
|
||||||
|
The function type arrow associates to the right.
|
||||||
|
|
||||||
|
Example,
|
||||||
|
```sophia
|
||||||
|
'a => list('a) => (int * list('a))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Statements
|
||||||
|
|
||||||
|
Function bodies are blocks of *statements*, where a statement is one of the following
|
||||||
|
|
||||||
|
```c
|
||||||
|
Stmt ::= 'switch' '(' Expr ')' Block(Case)
|
||||||
|
| 'if' '(' Expr ')' Block(Stmt)
|
||||||
|
| 'elif' '(' Expr ')' Block(Stmt)
|
||||||
|
| 'else' Block(Stmt)
|
||||||
|
| 'let' LetDef
|
||||||
|
| Using
|
||||||
|
| Expr
|
||||||
|
|
||||||
|
LetDef ::= Id Args [':' Type] '=' Block(Stmt) // Function definition
|
||||||
|
| Pattern '=' Block(Stmt) // Value definition
|
||||||
|
|
||||||
|
Case ::= Pattern '=>' Block(Stmt)
|
||||||
|
| Pattern Block(GuardedCase)
|
||||||
|
|
||||||
|
GuardedCase ::= '|' Sep1(Expr, ',') '=>' Block(Stmt)
|
||||||
|
|
||||||
|
Pattern ::= Expr
|
||||||
|
```
|
||||||
|
|
||||||
|
`if` statements can be followed by zero or more `elif` statements and an optional final `else` statement. For example,
|
||||||
|
|
||||||
|
```sophia
|
||||||
|
let x : int = 4
|
||||||
|
switch(f(x))
|
||||||
|
None => 0
|
||||||
|
Some(y) =>
|
||||||
|
if(y > 10)
|
||||||
|
"too big"
|
||||||
|
elif(y < 3)
|
||||||
|
"too small"
|
||||||
|
else
|
||||||
|
"just right"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Expressions
|
||||||
|
|
||||||
|
```c
|
||||||
|
Expr ::= '(' LamArgs ')' '=>' Block(Stmt) // Anonymous function (x) => x + 1
|
||||||
|
| '(' BinOp ')' // Operator lambda (+)
|
||||||
|
| 'if' '(' Expr ')' Expr 'else' Expr // If expression if(x < y) y else x
|
||||||
|
| Expr ':' Type // Type annotation 5 : int
|
||||||
|
| Expr BinOp Expr // Binary operator x + y
|
||||||
|
| UnOp Expr // Unary operator ! b
|
||||||
|
| Expr '(' Sep(Expr, ',') ')' // Application f(x, y)
|
||||||
|
| Expr '.' Id // Projection state.x
|
||||||
|
| Expr '[' Expr ']' // Map lookup map[key]
|
||||||
|
| Expr '{' Sep(FieldUpdate, ',') '}' // Record or map update r{ fld[key].x = y }
|
||||||
|
| '[' Sep(Expr, ',') ']' // List [1, 2, 3]
|
||||||
|
| '[' Expr '|' Sep(Generator, ',') ']'
|
||||||
|
// List comprehension [k | x <- [1], if (f(x)), let k = x+1]
|
||||||
|
| '[' Expr '..' Expr ']' // List range [1..n]
|
||||||
|
| '{' Sep(FieldUpdate, ',') '}' // Record or map value {x = 0, y = 1}, {[key] = val}
|
||||||
|
| '(' Expr ')' // Parens (1 + 2) * 3
|
||||||
|
| '(' Expr '=' Expr ')' // Assign pattern (y = x::_)
|
||||||
|
| Id | Con | QId | QCon // Identifiers x, None, Map.member, AELib.Token
|
||||||
|
| Int | Bytes | String | Char // Literals 123, 0xff, #00abc123, "foo", '%'
|
||||||
|
| AccountAddress | ContractAddress // Chain identifiers
|
||||||
|
| Signature // Signature
|
||||||
|
| '???' // Hole expression 1 + ???
|
||||||
|
|
||||||
|
Generator ::= Pattern '<-' Expr // Generator
|
||||||
|
| 'if' '(' Expr ')' // Guard
|
||||||
|
| LetDef // Definition
|
||||||
|
|
||||||
|
LamArgs ::= '(' Sep(LamArg, ',') ')'
|
||||||
|
LamArg ::= Id [':' Type]
|
||||||
|
|
||||||
|
FieldUpdate ::= Path '=' Expr
|
||||||
|
Path ::= Id // Record field
|
||||||
|
| '[' Expr ']' // Map key
|
||||||
|
| Path '.' Id // Nested record field
|
||||||
|
| Path '[' Expr ']' // Nested map key
|
||||||
|
|
||||||
|
BinOp ::= '||' | '&&' | '<' | '>' | '=<' | '>=' | '==' | '!='
|
||||||
|
| '::' | '++' | '+' | '-' | '*' | '/' | 'mod' | '^'
|
||||||
|
| 'band' | 'bor' | 'bxor' | '<<' | '>>' | '|>'
|
||||||
|
UnOp ::= '-' | '!' | 'bnot'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Operators types
|
||||||
|
|
||||||
|
| Operators | Type
|
||||||
|
| --- | ---
|
||||||
|
| `-` `+` `*` `/` `mod` `^` | arithmetic operators
|
||||||
|
| `!` `&&` `\|\|` | logical operators
|
||||||
|
| `band` `bor` `bxor` `bnot` `<<` `>>` | bitwise operators
|
||||||
|
| `==` `!=` `<` `>` `=<` `>=` | comparison operators
|
||||||
|
| `::` `++` | list operators
|
||||||
|
| `\|>` | functional operators
|
||||||
|
|
||||||
|
## Operator precedence
|
||||||
|
|
||||||
|
In order of highest to lowest precedence.
|
||||||
|
|
||||||
|
| Operators | Associativity
|
||||||
|
| --- | ---
|
||||||
|
| `!` `bnot`| right
|
||||||
|
| `^` | left
|
||||||
|
| `*` `/` `mod` | left
|
||||||
|
| `-` (unary) | right
|
||||||
|
| `+` `-` | left
|
||||||
|
| `<<` `>>` | left
|
||||||
|
| `::` `++` | right
|
||||||
|
| `<` `>` `=<` `>=` `==` `!=` | none
|
||||||
|
| `band` | left
|
||||||
|
| `bxor` | left
|
||||||
|
| `bor` | left
|
||||||
|
| `&&` | right
|
||||||
|
| `\|\|` | right
|
||||||
|
| `\|>` | left
|
||||||
+159
-165
@@ -7,16 +7,8 @@
|
|||||||
|
|
||||||
-include("$gsc_include/gsc.hrl").
|
-include("$gsc_include/gsc.hrl").
|
||||||
|
|
||||||
-type td_target()
|
|
||||||
:: ct
|
|
||||||
| iface
|
|
||||||
| ns
|
|
||||||
| pragma
|
|
||||||
| include
|
|
||||||
| using
|
|
||||||
.
|
|
||||||
|
|
||||||
-record(ct,
|
-record(td_ct,
|
||||||
{payable = none :: none | false | {true, tk()},
|
{payable = none :: none | false | {true, tk()},
|
||||||
main = none :: none | false | {true, tk()},
|
main = none :: none | false | {true, tk()},
|
||||||
contract = none :: none | tk(),
|
contract = none :: none | tk(),
|
||||||
@@ -24,24 +16,46 @@
|
|||||||
impls = none :: none | [tk()],
|
impls = none :: none | [tk()],
|
||||||
eq = none :: none | tk()}).
|
eq = none :: none | tk()}).
|
||||||
|
|
||||||
-type td_meta() :: #ct{}.
|
-type td_meta() :: #td_ct{}.
|
||||||
|
|
||||||
|
-record(decl_type,
|
||||||
|
{type = none :: none | tk(),
|
||||||
|
id = none :: none | tk(),
|
||||||
|
params = none :: none | [tk()],
|
||||||
|
eq = none :: none | tk()}).
|
||||||
|
|
||||||
|
-type decl_meta() :: #decl_type{}.
|
||||||
|
|
||||||
|
|
||||||
-type ast_meta() :: file
|
-type ast_meta() :: file
|
||||||
| block
|
|
||||||
| block_item
|
|
||||||
| td_meta()
|
| td_meta()
|
||||||
|
| decl_meta()
|
||||||
| nyi
|
| nyi
|
||||||
| {nyi, any()}
|
| {nyi, any()}
|
||||||
.
|
.
|
||||||
|
|
||||||
|
|
||||||
|
-type td_target()
|
||||||
|
:: td_ct
|
||||||
|
| td_iface
|
||||||
|
| td_ns
|
||||||
|
| td_pragma
|
||||||
|
| td_include
|
||||||
|
| using
|
||||||
|
.
|
||||||
|
|
||||||
-type s2t_target()
|
-type s2t_target()
|
||||||
:: file
|
:: file
|
||||||
| {block_of, s2t_target()}
|
| top_decl
|
||||||
|
| td_target()
|
||||||
| nyi
|
| nyi
|
||||||
| {nyi, any()}
|
| {nyi, any()}
|
||||||
.
|
.
|
||||||
|
|
||||||
|
-type s2f_target()
|
||||||
|
:: {block_of, s2t_target()}
|
||||||
|
.
|
||||||
|
|
||||||
|
|
||||||
-type ast() :: ntree(ast_meta(), tk()).
|
-type ast() :: ntree(ast_meta(), tk()).
|
||||||
-type asf() :: nforest(ast_meta(), tk()).
|
-type asf() :: nforest(ast_meta(), tk()).
|
||||||
@@ -51,7 +65,7 @@ main() ->
|
|||||||
HelloP = ts_utils:ct_file_abspath(HelloN),
|
HelloP = ts_utils:ct_file_abspath(HelloN),
|
||||||
{ok, HelloS} = file:read_file(HelloP),
|
{ok, HelloS} = file:read_file(HelloP),
|
||||||
S0 = gsc:unsafe_signal_from_file(HelloP),
|
S0 = gsc:unsafe_signal_from_file(HelloP),
|
||||||
T1 = s2t_file(S0),
|
T1 = s2t(file, S0),
|
||||||
io:format("hello.aes:~n", []),
|
io:format("hello.aes:~n", []),
|
||||||
io:format("```~n", []),
|
io:format("```~n", []),
|
||||||
io:format("~ts", [HelloS]),
|
io:format("~ts", [HelloS]),
|
||||||
@@ -59,16 +73,6 @@ main() ->
|
|||||||
io:format("AST: ~tp~n", [T1]),
|
io:format("AST: ~tp~n", [T1]),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
|
||||||
-spec s2t(ParseTarget, Signal) -> Tree when
|
|
||||||
ParseTarget :: s2t_target(),
|
|
||||||
Signal :: [tk()],
|
|
||||||
Tree :: ast().
|
|
||||||
|
|
||||||
s2t(_, _) ->
|
|
||||||
error(nyi).
|
|
||||||
|
|
||||||
|
|
||||||
% // Hello World Contract
|
% // Hello World Contract
|
||||||
% // Copyright (c) 2025 QPQ AG
|
% // Copyright (c) 2025 QPQ AG
|
||||||
%
|
%
|
||||||
@@ -80,188 +84,147 @@ s2t(_, _) ->
|
|||||||
% entrypoint hello(): string =
|
% entrypoint hello(): string =
|
||||||
% "hello, world"
|
% "hello, world"
|
||||||
|
|
||||||
|
-spec s2t(ParseTarget, Signal) -> AST when
|
||||||
-spec s2t_file(Signal) -> AstFile when
|
ParseTarget :: file,
|
||||||
Signal :: [tk()],
|
Signal :: [tk()],
|
||||||
AstFile :: #ns{meta :: file, kids :: asf()}.
|
AST :: ast().
|
||||||
|
|
||||||
s2t_file([]) ->
|
|
||||||
error(empty_file);
|
|
||||||
s2t_file(S0 = [#tk{pos = {_, FileCol}} | _]) ->
|
|
||||||
Blk0 = s2t_gulp_block(FileCol, S0),
|
|
||||||
Blk1 = t2t_parse_tds_in_block(Blk0),
|
|
||||||
#ns{meta = file, kids = [Blk1]}.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-spec s2t_gulp_block(BlkCol, Signal) -> Block when
|
|
||||||
BlkCol :: pos_integer(),
|
|
||||||
Signal :: [tk()],
|
|
||||||
Block :: #ns{meta :: block}.
|
|
||||||
|
|
||||||
s2t_gulp_block(BCol, Tks) ->
|
|
||||||
% sanity check
|
|
||||||
InBlock = fun(#tk{pos = {_, TCol}}) -> BCol =< TCol end,
|
|
||||||
true = lists:all(InBlock, Tks),
|
|
||||||
BlockItems = s2f_block_items(BCol, Tks),
|
|
||||||
#ns{meta = block, kids = BlockItems}.
|
|
||||||
|
|
||||||
|
|
||||||
-spec s2f_block_items(BCol, Signal) -> BlkItems when
|
|
||||||
BCol :: pos_integer(),
|
|
||||||
Signal :: [tk()],
|
|
||||||
BlkItems :: [BlkItem],
|
|
||||||
BlkItem :: #ns{meta :: block_item,
|
|
||||||
kids :: asf()}.
|
|
||||||
|
|
||||||
s2f_block_items(BCol, Signal) ->
|
|
||||||
s2f_block_items(BCol, [], Signal).
|
|
||||||
|
|
||||||
|
|
||||||
s2f_block_items(_BCol, Stk, []) ->
|
|
||||||
lists:reverse(Stk);
|
|
||||||
s2f_block_items(BCol, Stk, [#tk{pos = {_, BCol}} = T0 | F0]) ->
|
|
||||||
{slurp, BlkItem, F1} = s2t_slurp_block_item(BCol, T0, F0),
|
|
||||||
s2f_block_items(BCol, [BlkItem | Stk], F1).
|
|
||||||
|
|
||||||
|
|
||||||
s2t_slurp_block_item(BCol, T0, F0) ->
|
|
||||||
{ItemTokens, F1} = s2s_sw_block_item(BCol, T0, F0),
|
|
||||||
Item = #ns{meta = block_item, kids = ItemTokens},
|
|
||||||
{slurp, Item, F1}.
|
|
||||||
|
|
||||||
% sw = splitwith; kind of take/drop
|
|
||||||
s2s_sw_block_item(BCol, T0, F0) ->
|
|
||||||
InItem = fun(#tk{pos = {_, TCol}}) -> BCol < TCol end,
|
|
||||||
{F0_II, F1} = lists:splitwith(InItem, F0),
|
|
||||||
{[T0 | F0_II], F1}.
|
|
||||||
|
|
||||||
|
|
||||||
% File ::= Block(TopDecl)
|
% File ::= Block(TopDecl)
|
||||||
%
|
s2t(file, Signal) ->
|
||||||
|
case Signal of
|
||||||
|
[] -> error(empty_file);
|
||||||
|
_ -> {ns, file, s2f({block_of, top_decl}, Signal)}
|
||||||
|
end;
|
||||||
% TopDecl ::= ['payable'] ['main'] 'contract' Con [Implement] '=' Block(Decl)
|
% TopDecl ::= ['payable'] ['main'] 'contract' Con [Implement] '=' Block(Decl)
|
||||||
% | ['payable'] 'contract' 'interface' Con [Implement] '=' Block(Decl)
|
% | ['payable'] 'contract' 'interface' Con [Implement] '=' Block(Decl)
|
||||||
% | 'namespace' Con '=' Block(Decl)
|
% | 'namespace' Con '=' Block(Decl)
|
||||||
% | '@compiler' PragmaOp Version
|
% | '@compiler' PragmaOp Version
|
||||||
% | 'include' String
|
% | 'include' String
|
||||||
% | Using
|
% | Using
|
||||||
|
s2t(top_decl, Signal) ->
|
||||||
|
NewTarget =
|
||||||
|
case gsc_tokens:strings(3, Signal) of
|
||||||
|
["payable", "contract", "interface"] -> td_iface;
|
||||||
|
["contract", "interface" | _] -> td_iface;
|
||||||
|
["payable", "main", "contract"] -> td_ct;
|
||||||
|
["payable", "contract" | _] -> td_ct;
|
||||||
|
["contract" | _] -> td_ct;
|
||||||
|
["namespace" | _] -> td_namespace;
|
||||||
|
["@compiler" | _] -> td_pragma;
|
||||||
|
["include" | _] -> td_include;
|
||||||
|
["using" | _] -> using
|
||||||
|
end,
|
||||||
|
s2t(NewTarget, Signal);
|
||||||
|
% ['payable'] ['main'] 'contract' Con [Implement] '=' Block(Decl)
|
||||||
|
s2t(td_ct, S0) ->
|
||||||
|
{slurp, CtMeta, S1} = s2s_slurp_meta(#td_ct{}, S0),
|
||||||
|
{ns, CtMeta, s2f({block_of, decl}, S1)};
|
||||||
|
% Decl ::= 'type' Id ['(' TVar* ')'] '=' TypeAlias
|
||||||
|
% | 'record' Id ['(' TVar* ')'] '=' RecordType
|
||||||
|
% | 'datatype' Id ['(' TVar* ')'] '=' DataType
|
||||||
|
% | 'let' Id [':' Type] '=' Expr
|
||||||
|
% | (EModifier* 'entrypoint' | FModifier* 'function') Block(FunDecl)
|
||||||
|
% | Using
|
||||||
|
s2t(decl, S0) ->
|
||||||
|
NewTarget =
|
||||||
|
case gsc_tokens:strings(3, S0) of
|
||||||
|
["type" | _] -> decl_type;
|
||||||
|
["record" | _] -> decl_record;
|
||||||
|
["datatype" | _] -> decl_datatype;
|
||||||
|
["let" | _] -> decl_let;
|
||||||
|
Pfx3 ->
|
||||||
|
IsEp = lists:member("entrypoint", Pfx3),
|
||||||
|
IsFn = lists:member("function", Pfx3),
|
||||||
|
if
|
||||||
|
IsEp -> decl_entrypoint;
|
||||||
|
IsFn -> decl_function;
|
||||||
|
true -> error({bad_decl, S0})
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
s2t(NewTarget, S0);
|
||||||
|
% 'type' Id ['(' TVar* ')'] '=' TypeAlias
|
||||||
|
s2t(decl_type, S0) ->
|
||||||
|
{slurp, Meta, S1} = s2s_slurp_meta(#decl_type{}, S0),
|
||||||
|
{ns, Meta, s2t(type, S1)};
|
||||||
|
s2t(nyi, Signal) ->
|
||||||
|
{ns, nyi, Signal};
|
||||||
|
s2t(NYI = {nyi, _}, Signal) ->
|
||||||
|
{ns, NYI, Signal};
|
||||||
|
s2t(NYI, Signal) ->
|
||||||
|
{ns, {nyi, NYI}, Signal}.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-spec t2t_parse_tds_in_block(Block0) -> Block1 when
|
-spec s2f(ForestTarget, Signal) -> Forest when
|
||||||
Block0 :: ast(),
|
ForestTarget :: s2f_target(),
|
||||||
Block1 :: ast().
|
Signal :: [tk()],
|
||||||
|
Forest :: asf().
|
||||||
|
|
||||||
% go through and convert the block_item nodes to top
|
s2f({block_of, TreeTarget}, S0) ->
|
||||||
% decls
|
{gulp, Items} = gsc_signal:gulp_block_items(S0),
|
||||||
t2t_parse_tds_in_block(B0 = #ns{meta = block, kids = F0}) ->
|
[s2t(TreeTarget, I) || I <- Items].
|
||||||
F1 = lists:map(fun t2t_parse_td_from_item/1, F0),
|
|
||||||
B0#ns{kids = F1}.
|
|
||||||
|
|
||||||
|
|
||||||
-spec t2t_parse_td_from_item(BlockItem) -> TopDecl when
|
-spec s2s_slurp_meta(InitMeta, Signal) -> Result when
|
||||||
BlockItem :: #ns{meta :: block_item},
|
InitMeta :: Meta,
|
||||||
TopDecl :: #ns{meta :: td_meta()}.
|
Signal :: [tk()],
|
||||||
|
Result :: {slurp, Meta, NewSignal},
|
||||||
|
Meta :: ast_meta(),
|
||||||
|
NewSignal :: Signal.
|
||||||
|
|
||||||
t2t_parse_td_from_item(#ns{meta = block_item, kids = Signal}) ->
|
s2s_slurp_meta(M = #td_ct{}, S) ->
|
||||||
s2t_top_decl(Signal).
|
s2s_sm_td_ct(M, S);
|
||||||
|
s2s_slurp_meta(M = #decl_type{}, S) ->
|
||||||
|
s2s_sm_decl_type(M, S);
|
||||||
|
s2s_slurp_meta(M, S) ->
|
||||||
|
error({s2s_slurp_meta, M, S}).
|
||||||
|
|
||||||
|
|
||||||
-spec s2t_top_decl(Signal) -> TdTree when
|
s2s_sm_td_ct(Ct = #td_ct{payable = none}, S0) ->
|
||||||
Signal :: [tk()],
|
|
||||||
TdTree :: ast().
|
|
||||||
|
|
||||||
s2t_top_decl(S0) ->
|
|
||||||
case choose_td_target(S0) of
|
|
||||||
ct ->
|
|
||||||
s2t_ct(S0);
|
|
||||||
iface ->
|
|
||||||
f2t_nyi(iface, S0);
|
|
||||||
namespace ->
|
|
||||||
f2t_nyi(namespace, S0);
|
|
||||||
pragma ->
|
|
||||||
f2t_nyi(pragma, S0);
|
|
||||||
include ->
|
|
||||||
f2t_nyi(include, S0);
|
|
||||||
using ->
|
|
||||||
f2t_nyi(using, S0)
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-spec choose_td_target(Signal) -> TdTarget when
|
|
||||||
Signal :: [tk()],
|
|
||||||
TdTarget :: td_target().
|
|
||||||
|
|
||||||
choose_td_target(Signal) ->
|
|
||||||
case gsc_tokens:strings(3, Signal) of
|
|
||||||
["payable", "contract", "interface"] -> iface;
|
|
||||||
["contract", "interface" | _] -> iface;
|
|
||||||
["payable", "main", "contract"] -> ct;
|
|
||||||
["payable", "contract" | _] -> ct;
|
|
||||||
["contract" | _] -> ct;
|
|
||||||
["namespace" | _] -> namespace;
|
|
||||||
["@compiler" | _] -> pragma;
|
|
||||||
["include" | _] -> include;
|
|
||||||
["using" | _] -> using
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-spec s2t_ct(Signal) -> CtAst when
|
|
||||||
Signal :: [tk()],
|
|
||||||
CtAst :: ast().
|
|
||||||
|
|
||||||
s2t_ct(S0) ->
|
|
||||||
s2t_ct(#ct{}, S0).
|
|
||||||
|
|
||||||
s2t_ct(Ct = #ct{payable = none}, S0) ->
|
|
||||||
case S0 of
|
case S0 of
|
||||||
[#tk{str = "payable"} = T0 | S1] ->
|
[#tk{str = "payable"} = T0 | S1] ->
|
||||||
s2t_ct(Ct#ct{payable = {true, T0}}, S1);
|
s2s_sm_td_ct(Ct#td_ct{payable = {true, T0}}, S1);
|
||||||
_ ->
|
_ ->
|
||||||
s2t_ct(Ct#ct{payable = false}, S0)
|
s2s_sm_td_ct(Ct#td_ct{payable = false}, S0)
|
||||||
end;
|
end;
|
||||||
s2t_ct(Ct = #ct{main = none}, S0) ->
|
s2s_sm_td_ct(Ct = #td_ct{main = none}, S0) ->
|
||||||
case S0 of
|
case S0 of
|
||||||
[#tk{str = "main"} = T0 | S1] ->
|
[#tk{str = "main"} = T0 | S1] ->
|
||||||
s2t_ct(Ct#ct{main = {true, T0}}, S1);
|
s2s_sm_td_ct(Ct#td_ct{main = {true, T0}}, S1);
|
||||||
_ ->
|
_ ->
|
||||||
s2t_ct(Ct#ct{main = false}, S0)
|
s2s_sm_td_ct(Ct#td_ct{main = false}, S0)
|
||||||
end;
|
end;
|
||||||
s2t_ct(Ct = #ct{contract = none}, S0) ->
|
s2s_sm_td_ct(Ct = #td_ct{contract = none}, S0) ->
|
||||||
case S0 of
|
case S0 of
|
||||||
[#tk{str = "contract"} = T0 | S1] ->
|
[#tk{str = "contract"} = T0 | S1] ->
|
||||||
s2t_ct(Ct#ct{contract = T0}, S1);
|
s2s_sm_td_ct(Ct#td_ct{contract = T0}, S1);
|
||||||
_ ->
|
_ ->
|
||||||
error({no_kwd_contract, Ct, S0})
|
error({no_kwd_contract, Ct, S0})
|
||||||
end;
|
end;
|
||||||
s2t_ct(Ct = #ct{con = none}, S0) ->
|
s2s_sm_td_ct(Ct = #td_ct{con = none}, S0) ->
|
||||||
case S0 of
|
case S0 of
|
||||||
[#tk{shape = con} = T0 | S1] ->
|
[#tk{shape = con} = T0 | S1] ->
|
||||||
s2t_ct(Ct#ct{con = T0}, S1);
|
s2s_sm_td_ct(Ct#td_ct{con = T0}, S1);
|
||||||
_ ->
|
_ ->
|
||||||
error({no_contract_name, Ct, S0})
|
error({no_contract_name, Ct, S0})
|
||||||
end;
|
end;
|
||||||
s2t_ct(Ct = #ct{impls = none}, S0) ->
|
s2s_sm_td_ct(Ct = #td_ct{impls = none}, S0) ->
|
||||||
case gsc_tokens:strings(1, S0) of
|
case gsc_tokens:strings(1, S0) of
|
||||||
[":"] ->
|
[":"] ->
|
||||||
{slurp, Impls, S1} = s2f_slurp_impls(S0),
|
{slurp, Impls, S1} = s2f_slurp_impls(S0),
|
||||||
s2t_ct(Ct#ct{impls = Impls}, S1);
|
s2s_sm_td_ct(Ct#td_ct{impls = Impls}, S1);
|
||||||
_ ->
|
_ ->
|
||||||
s2t_ct(Ct#ct{impls = []}, S0)
|
s2s_sm_td_ct(Ct#td_ct{impls = []}, S0)
|
||||||
end;
|
end;
|
||||||
s2t_ct(Ct = #ct{eq = none}, S0) ->
|
s2s_sm_td_ct(Ct = #td_ct{eq = none}, S0) ->
|
||||||
case S0 of
|
case S0 of
|
||||||
[#tk{str = "="} = T0 | S1] ->
|
[#tk{str = "="} = T0 | S1] ->
|
||||||
s2t_ct(Ct#ct{eq = T0}, S1);
|
s2s_sm_td_ct(Ct#td_ct{eq = T0}, S1);
|
||||||
_ ->
|
_ ->
|
||||||
error({no_equal_sign, Ct, S0})
|
error({no_equal_sign, Ct, S0})
|
||||||
end;
|
end;
|
||||||
s2t_ct(Ct, S0) ->
|
s2s_sm_td_ct(Ct, S0) ->
|
||||||
#ns{meta = Ct, kids = S0}.
|
{slurp, Ct, S0}.
|
||||||
|
|
||||||
|
|
||||||
s2f_slurp_impls([#tk{str = ":"}, #tk{shape = con} = I0 | S0]) ->
|
s2f_slurp_impls([#tk{str = ":"}, #tk{shape = con} = I0 | S0]) ->
|
||||||
s2f_slurp_impls([I0], S0).
|
s2f_slurp_impls([I0], S0).
|
||||||
@@ -272,8 +235,39 @@ s2f_slurp_impls(Stk, S0) ->
|
|||||||
{slurp, lists:reverse(Stk), S0}.
|
{slurp, lists:reverse(Stk), S0}.
|
||||||
|
|
||||||
|
|
||||||
f2t_nyi(F) ->
|
%-record(decl_type,
|
||||||
{ns, nyi, F}.
|
% {type = none :: none | tk(),
|
||||||
|
% id = none :: none | tk(),
|
||||||
|
% params = none :: none | [tk()],
|
||||||
|
% eq = none :: none | tk()}).
|
||||||
|
|
||||||
f2t_nyi(Why, F) ->
|
s2s_sm_decl_type(M = #decl_type{type = none}, S0) ->
|
||||||
{ns, {nyi, Why}, F}.
|
case S0 of
|
||||||
|
[#tk{str = "type"} = T0 | S1] ->
|
||||||
|
s2s_sm_decl_type(M#decl_type{type = T0}, S1);
|
||||||
|
_ ->
|
||||||
|
error({no_kwd_type, S0})
|
||||||
|
end;
|
||||||
|
s2s_sm_decl_type(M = #decl_type{id = none}, S0) ->
|
||||||
|
case S0 of
|
||||||
|
[#tk{shape = id} = T0 | S1] ->
|
||||||
|
s2s_sm_decl_type(M#decl_type{id = T0}, S1);
|
||||||
|
_ ->
|
||||||
|
error({no_type_id, S0})
|
||||||
|
end;
|
||||||
|
s2s_sm_decl_type(M = #decl_type{params = none}, S0) ->
|
||||||
|
case S0 of
|
||||||
|
[#tk{str = "("} = T0 | _] ->
|
||||||
|
error({fixme, parens_bad});
|
||||||
|
_ ->
|
||||||
|
s2s_sm_decl_type(M#decl_type{params = []}, S0)
|
||||||
|
end;
|
||||||
|
s2s_sm_decl_type(M = #decl_type{eq = none}, S0) ->
|
||||||
|
case S0 of
|
||||||
|
[#tk{str = "="} = T0 | S1] ->
|
||||||
|
s2s_sm_decl_type(M#decl_type{eq = T0}, S1);
|
||||||
|
_ ->
|
||||||
|
error({no_equal_sign, S0})
|
||||||
|
end;
|
||||||
|
s2s_sm_decl_type(M, S0) ->
|
||||||
|
{slurp, M, S0}.
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
% signal = non-noisy tokens
|
||||||
|
-module(gsc_signal).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
from_tokens/1,
|
||||||
|
is_block/1,
|
||||||
|
gulp_block_items/1,
|
||||||
|
block_to_items/1,
|
||||||
|
take_block_item/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
-include("$gsc_include/gsc.hrl").
|
||||||
|
|
||||||
|
|
||||||
|
-spec from_tokens(Tokens) -> Signal when
|
||||||
|
Tokens :: [tk()],
|
||||||
|
Signal :: [tk()].
|
||||||
|
% @doc filter out comments/whitespace
|
||||||
|
|
||||||
|
from_tokens(Tokens) ->
|
||||||
|
gsc_tokens:filter_significant(Tokens).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-spec is_block(Signal) -> Result when
|
||||||
|
Signal :: [tk()],
|
||||||
|
Result :: boolean().
|
||||||
|
|
||||||
|
is_block([]) ->
|
||||||
|
true;
|
||||||
|
is_block([#tk{pos = {_, BCol}} | Rest]) ->
|
||||||
|
InBlock =
|
||||||
|
fun(#tk{pos = {_, TCol}}) ->
|
||||||
|
BCol =< TCol
|
||||||
|
end,
|
||||||
|
lists:all(InBlock, Rest).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-spec gulp_block_items(Signal) -> Result when
|
||||||
|
Signal :: [tk()],
|
||||||
|
Result :: {slurp, Items, NewSignal}
|
||||||
|
| {error, any()},
|
||||||
|
Items :: [Signal],
|
||||||
|
NewSignal :: Signal.
|
||||||
|
|
||||||
|
gulp_block_items(S) ->
|
||||||
|
case is_block(S) of
|
||||||
|
true -> {gulp, block_to_items(S)};
|
||||||
|
false -> find_badness(S)
|
||||||
|
end.
|
||||||
|
|
||||||
|
find_badness([#tk{pos = {_, StartCol}} = StartTk | Rest]) ->
|
||||||
|
find_badness(StartCol, StartTk, Rest).
|
||||||
|
|
||||||
|
find_badness(StartCol, StartTk, [#tk{pos = {_, TkCol}} = Tk | Rest]) ->
|
||||||
|
Bad = TkCol < StartCol,
|
||||||
|
case Bad of
|
||||||
|
false -> find_badness(StartCol, StartTk, Rest);
|
||||||
|
true -> {error, {bad_block, [{start_col, StartCol},
|
||||||
|
{end_col, TkCol},
|
||||||
|
{start_tk, StartTk},
|
||||||
|
{end_tk, Tk}]}}
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-spec block_to_items(Signal) -> BlockItems when
|
||||||
|
Signal :: [tk()],
|
||||||
|
BlockItems :: [Signal].
|
||||||
|
% @doc
|
||||||
|
% naive algorithm, so doesn't ensure all block items
|
||||||
|
% are same indent level
|
||||||
|
%
|
||||||
|
% Input:
|
||||||
|
% foo = ...
|
||||||
|
% bar = ...
|
||||||
|
% baz = ...
|
||||||
|
%
|
||||||
|
% Output:
|
||||||
|
% [foo = ...,
|
||||||
|
% bar = ...,
|
||||||
|
% baz = ...]
|
||||||
|
block_to_items([]) ->
|
||||||
|
[];
|
||||||
|
block_to_items(S) ->
|
||||||
|
b2is([], S).
|
||||||
|
|
||||||
|
b2is(Acc, []) ->
|
||||||
|
lists:reverse(Acc);
|
||||||
|
b2is(Acc, S) ->
|
||||||
|
{Item, S1} = take_block_item(S),
|
||||||
|
b2is([Item | Acc], S1).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-spec take_block_item(Signal) -> Result when
|
||||||
|
Signal :: [tk()],
|
||||||
|
Result :: {Item, NewSignal},
|
||||||
|
Item :: Signal,
|
||||||
|
NewSignal :: Signal.
|
||||||
|
|
||||||
|
take_block_item([]) ->
|
||||||
|
{[], []};
|
||||||
|
take_block_item([#tk{pos = {_, ICol}} = T0 | S0]) ->
|
||||||
|
InItem =
|
||||||
|
fun(#tk{pos = {_, TCol}}) ->
|
||||||
|
ICol < TCol
|
||||||
|
end,
|
||||||
|
{S0_II, S1} = lists:splitwith(InItem, S0),
|
||||||
|
{[T0 | S0_II], S1}.
|
||||||
Reference in New Issue
Block a user