This commit is contained in:
2026-06-04 14:01:46 -07:00
parent fdb40dcb92
commit 10424927b1
4 changed files with 639 additions and 165 deletions
+77
View File
@@ -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) ->
+292
View File
@@ -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
View File
@@ -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}.
+111
View File
@@ -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}.