Skip to content

Instantly share code, notes, and snippets.

@easylogic
Last active December 19, 2023 03:39
Show Gist options
  • Save easylogic/32aea71e5f2c583b13cc1b1c663e6bfd to your computer and use it in GitHub Desktop.
Save easylogic/32aea71e5f2c583b13cc1b1c663e6bfd to your computer and use it in GitHub Desktop.
stylish language - CSS and Language - SaLang - 사랑( korean language)

사랑(SaLang - Stylish CSS based Language)

salang is css based language.

please show below code

span.MyComponent {
    // properties 
    --background-color: yellow;
    --label-color: black;
   
    // css 
    background-color: var(--background-color);
    
    label {
        span {
            color: var(--label-color);
            
            "안녕하세요";
        }
    }
    
    @click () {
      --background-color: blue;
      --label-color: yellow;
    }
}

Above code is converted to some files.

span.MyComponent {

    // properties 
    --background-color: yellow;
   
    // css 
    background-color: var(--background-color);
    
    label {
        span {
            color: var(--label-color);
        }
    }

}
class MyComponent extends UIElement {
    template() {
        return <span class="MyComponent">
          <label>
            <span>
              안녕하세요.
            </span>
          </label>
        </span>
    }
    
    [CLICK()] () {
      this.$el.cssText(`
        --background-color: blue;
        --label-color: yellow;
      `)
    }

}
@easylogic
Copy link
Author

/* 외부 컴포넌트 및 스타일 불러오기 */
@import 'my-ui-library/button-component';
@import 'my-ui-library/responsive-style';

/* 전역 스타일 정의 */
@global-style {
  primaryColor: #007bff;
  secondaryColor: #6c757d;
  fontSize: 16px;
  buttonWidth: 100px;
}

/* 접근성을 고려한 버튼 컴포넌트 정의 */
@component custom-button-component extends button {
  @attribute label: "Custom Button";
  @attribute ariaLabel: "Custom Accessible Button";
  @attribute role: "button";

  /* 스타일 정의 */
  @template-style(diff) {
    button(aria-label: var(ariaLabel), role: var(role)) {
      content: var(label);
      font-size: var(fontSize);
      width: var(buttonWidth);
      border: none;
      color: white;
      padding: 8px 16px;
      background-color: var(secondaryColor);

      &:hover {
        background-color: var(primaryColor);
      }
    }
  }

  /* 이벤트 처리 */
  @event click {
    console.log("Custom button clicked!");
  }
}

/* 멀티라인 텍스트 컴포넌트 */
@component multi-line-text-component {
  @template {
    pre {
      """
      여러 줄에 걸친 텍스트입니다.
      <pre> 태그 내에서 텍스트의 서식을 유지합니다.
      """
    }
  }
}

/* 반응형 리스트 컴포넌트 */
@component responsive-list {
  @state items: [];
  @media(max-width: 600px) { style: { list-style: "none" } }

  /* 리스트 아이템 정의 */
  @template(diff) {
    ul {
      for item in var(items) where item > 2 {
        li { content: "필터링된 값: " + item }
      }
    }
  }
}

/* 웹 페이지 컴포넌트 정의 */
@component web-page {
  @template {
    custom-button-component(label: "Click Me") { }
    multi-line-text-component { }
    responsive-list { }
  }
}

@easylogic
Copy link
Author

SaLang 업데이트

@component MyComponent {
  @attribute title: "My Amazing Component";
  @attribute isVisible: true;

  @style {
    .my-component {
      color: var(primaryColor);
      padding: 10px;
    }

    .hidden {
      display: none;
    }
  }

  @function toggleVisibility() {
    @js {
      this.isVisible = !var(isVisible);
    }
  }

  @event click {
    @selector .toggle-btn {
      background-color: var(secondaryColor);
    }
    @js {
      toggleVisibility();
    }
  }

  @template {
    div(class: "my-component" var(isVisible ? '' : ' hidden')) {
      "Title: " var(title)

      @if var(isVisible) {
        div { "This content is visible" }
      } @else {
        div { "This content is hidden" }
      }

      @repeat item in var(items) {
        @if var(item.isVisible) {
          div(class: "item") { "Item: " var(item.name) }
        } @else {
          div(class: "item hidden") { "Hidden Item" }
        }
      }

      button(class: "toggle-btn") { "Toggle Visibility" }
    }
  }
}

@easylogic
Copy link
Author

easylogic commented Dec 19, 2023

PEG Parser 업데이트

{
  // Helper function to remove whitespace nodes
  function filterWhitespace(nodes) {
    return nodes.filter(node => {
      if (typeof node === 'string') return node.trim() !== '';
      return node;
    });
  }

  // Helper function to create a text node
  function createTextNode(text) {
    return { type: 'TextNode', value: text.trim() };
  }
}

// The entry point of the grammar
Start
  = components:Component+ { return components; }

// Defines a component
Component
  = "@component" _ id:identifier _ "{" _ body:ComponentBody _ "}" {
      return { type: "Component", id, body };
    }

// Body of a component, can contain various types of content
ComponentBody
  = content:(_ Attribute _ / _ Event _ / _ Style _ / _ Function _ / _ Template _)* {
      return filterWhitespace(content.flat(Infinity));
    }

// Defines an attribute of a component
Attribute
  = "@attribute" _ id:identifier ":" _ value:attributeValue _ ";" {
      return { type: "Attribute", id, value };
    }

// Possible values for an attribute
attributeValue
  = StringLiteral / NumberLiteral / BooleanLiteral / identifier

// Defines an event handler
Event
  = "@event" _ eventName:identifier _ eventArgs:EventArgs? _ "{" _ eventBody:EventBody _ "}" {
      return { type: "Event", eventName, args: eventArgs || [], body: eventBody };
    }

// Event arguments
EventArgs
  = "(" _ argList:identifierList _ ")" {
      return argList;
    }

// List of identifiers, used for event arguments
identifierList
  = first:identifier _ rest:("," _ identifier)* {
      return [first, ...rest.map(r => r[2])];
    }

// Body of an event, can contain selectors and JavaScript sections
EventBody
  = sections:(_ SelectorRule _ / _ JavaScriptSection _)* {
      return filterWhitespace(sections.flat(Infinity));
    }

// JavaScript code section within an event
JavaScriptSection
  = "@js" _ "{" _ code:JavaScriptCode _ "}" {
      return { type: "JavaScript", code };
    }

// JavaScript code, can be a mix of text and variables
JavaScriptCode
  = code:(_ JavaScriptCodeChunk _)* {
      return filterWhitespace(code.flat(Infinity)).map(it => {
        if (typeof it === 'string') return { type: 'JavaScriptCodeChunk', value: it.trim() };
        return it;
      });
    }

// Chunk of JavaScript code, either a variable or plain text
JavaScriptCodeChunk
  = Variable / NonVariableChunk

// Plain text chunk within JavaScript code
NonVariableChunk
  = chars:[^{}]+ {
      return chars.join('');
    }

// Selector rule within an event or function
SelectorRule
  = "@selector" _ selector:selector _ "{" _ properties:CSSPropertiesWithVars _ "}" {
      return { type: "CSSRule", selector, properties };
    }

// CSS properties, allowing variables
CSSPropertiesWithVars
  = properties:(_ CSSPropertyWithVar _ / _ CSSProperty _)* {
      return filterWhitespace(properties.flat(Infinity));
    }

// CSS property that includes a variable
CSSPropertyWithVar
  = property:property _ ":" _ value:Variable _ ";" {
      return { type: "CSSPropertyWithVar", property, value };
    }

// Variable within CSS or JavaScript
Variable
  = "var(" _ varName:identifier _ ")" {
      return { type: "Variable", varName };
    }

// Defines a function
Function
  = "@function" _ functionName:identifier _ functionArgs:FunctionArgs? _ "{" _ functionBody:FunctionBody _ "}" {
      return { type: "Function", functionName, args: functionArgs ? functionArgs : [], body: functionBody };
    }

// Arguments of a function
FunctionArgs
  = "(" _ argList:identifierList _ ")" {
      return argList;
    }

// Body of a function, can contain selectors and JavaScript sections
FunctionBody
  = sections:( _ SelectorRule _ / _ JavaScriptSection _)* {
      return filterWhitespace(sections.flat(Infinity));
    }

// Defines CSS styles
Style
  = "@style" _ "{" _ rules:CSSRules _ "}" {
      return { type: "Style", rules };
    }

// Template of a component
Template
  = "@template" _ "{" _ content:TemplateContent _ "}" {
      return { type: "Template", content: filterWhitespace(content.flat(Infinity)) };
    }

// Content of a template, including conditional rendering, repeat rendering, elements, and text
TemplateContent
  = elements:(_ ConditionalRendering _ / _ RepeatRendering _ / _ Element _ / _ Text _ )* { 
    return filterWhitespace(elements.flat(Infinity)); 
}

// Conditional rendering rules
ConditionalRendering
  = _ ifContent:IfConditionalRendering _ elseContent:ElseConditionalRendering? _  {
      return { type: "Conditional", ifContent, elseContent };
    }

// 'If' part of conditional rendering
IfConditionalRendering
  = "@if" _ condition:JavaScriptCode _ "{" _ content:TemplateContent _ "}" {
      return { type: "IfConditional", condition, content };
    }

// 'Else' part of conditional rendering
ElseConditionalRendering
  = "@else" _ "{" _ content:TemplateContent _ "}" {
      return { type: "ElseConditional", content };
    }    

// Repeat rendering rule
RepeatRendering
  = "@repeat" _ variable:identifier _ "in" _ collection:JavaScriptCode _ "{" _ content:TemplateContent _ "}" {
      return { type: "Repeat", variable, collection, content };
    }

// HTML element
Element
  = tag:TagName _ "{" _ content:(_ Element _ / _ Text _)* _ "}" {
      return { type: "Element", tag, content: filterWhitespace(content.flat(Infinity)) };
    }

// Tag name of an HTML element
TagName
  = id:identifier { return id; }

// Text node
Text
  = chars: [^{}\n]+ { return createTextNode(chars.join('')); }  

// CSS rules
CSSRules
  = rules:CSSRule* { return rules; }

// Single CSS rule
CSSRule
  = selector:selector _ "{" _ properties:CSSProperties _ "}" {
      return { type: "CSSRule", selector, properties };
    }

// CSS properties
CSSProperties
  = properties:CSSProperty* { return properties; }

// Single CSS property
CSSProperty
  = property:property _ ":" _ value:value _ ";" {
      return { type: "CSSProperty", property, value };
    }

// Whitespace
_ "whitespace"
  = [ \t\n\r]*

// Identifier (includes complex identifiers with dot notation)
identifier "identifier"
  = head:[a-zA-Z_] tail:[a-zA-Z0-9_.]* { return head + tail.join(''); }

// String literal
StringLiteral "string"
  = DoubleQuotedString / SingleQuotedString / BacktickQuotedString / TripleDoubleQuotedString
  
DoubleQuotedString "double quoted string"  
  = "\"" chars:[^\"]* "\"" { return chars.join(''); }

SingleQuotedString "single quoted string"
  = "'" chars:[^']* "'" { return chars.join(''); }

BacktickQuotedString "backtick quoted string"
  = '`' chars:[^`]* '`' { return chars.join(''); }

TripleDoubleQuotedString "triple double quoted string"
  = '"""' chars:(TripleDoubleQuotedChar*) '"""' { return chars.join(''); }

TripleDoubleQuotedChar
  = !'"""' char:. { return char; }

// Number literal
NumberLiteral "NumberLiteral"
  = digits:[0-9]+ { return parseInt(digits.join('')); }

// Boolean literal
BooleanLiteral "BooleanLiteral"
  = "true" { return true; }
  / "false" { return false; }

// CSS selector
selector "CSS selector"
  = chars:[^{]+ { return chars.join('').trim(); }

// CSS property
property "CSS property"
  = chars:[^:]+ { return chars.join('').trim(); }

// CSS value
value "CSS value"
  = chars:[^;]+ { return chars.join('').trim(); }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment