site logo
  • Կայքի մասին
  • Ծրագրավորում
  • Ժեշտ
  • Անվտանգություն
  • Հարց ու Պատասխան (ՀուՊ)
Դեկտեմբեր 8, 2012  |  By armenbadal In

Հաշվարկիչից դեպի լեզվի ինտերպրետատոր

«Հաշվարկիչ կամ արտահայտությունների ինտերպրետատոր» հոդվածով ես սկսեցի պատմել, թե ինչպես կարելի է գրել մեծ ամբողջ թվերի հետ թվաբանական գործողություններ կատարող մի պարզ հաշվարկիչ՝ կալկուլյատոր։ Այդ հաշվարկիչը օգտագործողի հետ շփվում է երկխոսության ռեժիմում՝ հերթականությամբ հարցնելով արտահայտության տեքստը, հաշվարկելով այն և արտածելով հաշվարկման արդյունքները։

Երկրորդ հոդվածում, որ կոչվում է «Հաշվարկիչ վերագրման և արտածման հրամաններով», ես ընդլայնեցի հաշվարկիչն այնպես, որ այն հնարավորություն ունենա փոփոխականների մեջ պահել արտահայտության արժեքը, իսկ արտածման հրամանով արտածի այն։ Կարելի է ասել, որ պարզ հաշվարկիչը վերածվեց ամենապարզ ծրագրավորման լեզվի՝ վերագրման ու արտածման հրամաններով և թվաբանական արտահայտություններով։ Այս երկրորդ հաշվարկիչը նույնպես աշխատում է երկխոսության ռեժիմում։

Հաշվարկիչի այս հերթական զարգացումը նպատակ ունի առաջին քայլն անել REPL ցիկլից դեպի ծրագրավորման լեզվի ինտերպրետատոր։ Նախ՝ լեքսիկական անալիզատորը կփոփոխվի այնպես, որ հրամանային տողի փոխարեն ծրագրի տեքստը ընթերցվի ֆայլից։ Ապա՝ լեզվի հրամանների համակարգը կընդլայնվի ներածման և հաջորդման հրամաններով։

Ծրագրի կոդի ընթերցումը ֆայլից

Ձևափոխենք լեքսիկական անալիզատորն այնպես, որ ծրագրի նիշերն ընթերցվեն ոչ թե տողից, այլ bufio.Reader օբյեկտից։ Scanner կառուցվածքի նոր տեսքն ահա այսպիսինն է.

type Scanner struct {
  source *bufio.Reader
  line int
}

Որտեղ line փոփոխականը նախատեսված է տողի համարը պահելու համար։ Հետագայում այն մեզ հարկավոր է լինելու քերականական սխալների տեղը նշելիս։

Փոփոխության է ենթարկվում նաև Scanner-ի կոնստրուկտորը։ Տողի փոխարեն այն այժմ ստանում է bufio.Reader կառուցվածքի ցուցիչ։

func New(src *bufio.Reader) *Scanner {
  return &Scanner{src, 1}
}

Հոսքից նիշեր կարդալիս անհրաժեշտ է մի ֆունկցիա, որը վերադարձնում է հերթական նիշը, բայց այն չի հեռացնում հոսքից։ Եթե հոսքն արդեն դատարկ է, ապա վերադարձնում է 0։

func (s *Scanner) peek() rune {
  br, err := s.source.Peek(1)
  if err != nil { return 0 }
  return rune(br[0])
}

Մեկ այլ ֆունկցիա կարդում և վերադարձնում է հերթական սիմվոլը։ Այս դեպքում նույնպես, եթե հոսքն արդեն դատարկ է, ապա վերադարձնում է 0։

func (s *Scanner) char() rune {
  ch, _, err := s.source.ReadRune()
  if err != nil { return 0 }
  return ch
}

scanWith մեթոդն արդեն ձևափոխված է այնպես, որ օգտագործվի char մեթոդը։ Այստեղ ձևավորվում է նաև line դաշտի ընթացիկ արժեքը։

func (s* Scanner) scanWith(predicate func(r rune) bool) string {
  res := ""
  ch := s.char()
  for predicate(ch) {
    res += string(ch)
    if ch == '\n' { s.line++ }
    ch = s.char()
  }
  s.source.UnreadRune()
  return res
}

Փոքր փոփոխություններ է կրել նաև քերականական անալիզատորը. Parse մեթոդը արգումենտում ստանում է ոչ թե տող, այլ bufio.Reader օբյեկտի ցուցիչ։

func (p* Parser) Parse(src *bufio.Reader) astexec.Statement {
...
}

Այս փոփոխությունները բավական են, որպեսզի ֆայլի դեսկրիպտորից ստեղծվի bufio.Reader օբյեկտ և նրանից ընթերցվի ծրագիրը։

Լեզվի քերականության ընդլայնում

Հաշվարկիչի լեզուն ընդլայնված է երկու նոր հրամաններով՝ տվյալների ներածման և հրամաններ հաջորդման։

Sequence = Statement {';' Statement}.
Input = 'input' 'Ident'.

Հրամանների հաջորդում

Հաջորդման հրաման (կամ հրամանների հաջորդման կառուցվածք) ասելով կհասկանանք ծրագրի տեքստում իրար հետևից գրված հրամանները, որոնք բաժանված են “;” նիշով։ (Պետք է ուշադրություն դարձնել, որ ոչ թե ամեն մի հրաման ավարտվում է “;” նիշով, այլ նրանով հրամաններն իրարից բաժանվում են։)

Sequence կառուցվածքը պարունակում է միակ children դաշտը, որը պարունակում է հաջորդականություն կազմող հրամանների ցուցիչները։

type Sequence struct {
  children *list.List
}

Կոնստրուկտորը children դաշտն արժեքավորում է նոր ստեղծված list.List օբյեկտով։

func NewSequence() *Sequence {
  return &Sequence{children: list.New().Init()}
}

Քանի որ հրամանները քերականական անալիզատորի կողմից վերլուծվելու են հաջորդաբար (ամեն մի հրամանը վերլուծելիս դեռ հայտնի չէ, թե ևս քանի հրամաններ են հետևելու նրան), նախատեսված է Append մեթոդը, որը Sequence օբյեկտի children ցուցակում ավելացնում է հերթական տարրը։

func (q *Sequence) Append(st Statement) {
  q.children.PushBack(st)
}

Կատարման ժամանակ պարզապես հաջորդաբար կատարվում են children ցուցակի հրամանները (ճիշտ կլիներ, իհարկե, այստեղ ստուգել, որ ցուցակի տարրը nil չլինի)։

func (q *Sequence) Execute(env *environ.Environment) {
  for e := q.children.Front(); e != nil; e = e.Next() {
    st := e.Value.(Statement)
    st.Execute(env)
  }
}

Լեքսիկական անալիզատորի թոքենների ցուցակում ավելացնենք Semic թոքենը՝ “;” նիշի համար։

Քերականական անալիզատորում ավելացնենք sequence մեթոդը։ Այն ստեղծում է նոր Sequence օբյեկտ, ապա նրանում ավելացնում է հերթական վերլուծած հրամանները։

func (p *Parser) sequence() astexec.Statement {
  seq := astexec.NewSequence()
  st := p.statement()
  seq.Append(st)
  for p.look == scanner.Semic {
    p.match(scanner.Semic)
    seq.Append(p.statement())
  }
  return seq
}

Եվ նշենք, որ Parse մեթոդը ձևափոխված է այնպես, որ լեզվի վերլուծությունը սկսվի հենց sequence մեթոդից։

func (p *Parser) Parse(src *bufio.Reader) astexec.Statement {
  p.look = scanner.None
  p.scan = scanner.New(src)
  p.match(scanner.None)
  return p.sequence()
}

Ներածման հրաման

Ներածման հրամանը որոշվում է input ծառայողական բառով և նրան հետևող իդենտիֆիկատորով։ Այն օգտագործողից պահանջում է ստեղնաշարից ներմուծել ամբողջ թվի արժեքը և այդ արժեքը վերագրում է իր արգումենտի իդենտիֆիկատորին։

Աբստրակտ քերականական ծառում ներածման հրամանի հանգույցն ունի հետևյալ տեսքը, որտեղ name դաշտը այն փոփոխականի անունն է, որի համար պետք է կարդալ արժեքը։

type Input struct {
  name string
}

Կոնստրուկտորը պարզ է, այն քննարկաման ենթակա չէ.

func NewInput(nm string) *Input {
  return &Input{name: nm}
}

Ներածման հրամանը կատարելու համար ստեղծում ենք bufio.Reader օբյեկտ՝ os.Stdin ֆայլի հետ կապված։ Ապա կարդում ենք տող, նրանով արժեքավորում նոր big.Int օբյեկտ և փոփոխականի անունի հետ միասին ավելացում միջավայրում։

func (i *Input) Execute(env *environ.Environment) {
  reader := bufio.NewReader(os.Stdin)
  num, _ := reader.ReadString('\n')
  value := new(big.Int)
  value.SetString(num, 10)
  env.Set(i.name, value)
}

Parser կառուցվածքի inputval մեթոդը նախ ճանաչում է scanner.Input թոքենը, ապա փոփոխականի իդենտիֆիկատորը, վերջինիս համապատասխան լեքսեմն էլ օգտագործելով ստեղծում է նոր astexec.Input օբյեկտ։

func (p *Parser) inputval() astexec.Statement {
  p.match(scanner.Input)
  nm := p.lexeme
  p.match(scanner.Ident)
  return astexec.NewInput(nm)
}

Եվ ներածման հրամանը վերլուծության ենթական հրամանների շարքում ավելացնելու համար statement մեթոդում ավելացնում ենք նոր ճյուղ։

func (p *Parser) statement() astexec.Statement {
  switch p.look {
    case scanner.Ident:
      return p.assignment()
    case scanner.Print:
      return p.printexpr()
    case scanner.Input:
      return p.inputval()
  }
  return nil
}

Ծրագրի մուտքի կետը

main փաթեթի main ֆունկցիայում նախ ստուգում ենք, որ հրամանայի տողում տրված արումենտները լինեն ճիշտ երկու հատ՝ len(os.Args) != 2։ Ապա փորձում ենք os.Open ֆունկցիայով բացել ծրագրի ֆայլը՝ անհաջողության դեպքում տալով հաղորդագրություն։

func main() {
  if len(os.Args) != 2 {
    fmt.Println("Source file missing.")
    os.Exit(1)
  }

  fin, err := os.Open(os.Args[1])
  if err != nil {
    fmt.Println("Cannot open input file.")
    os.Exit(2)
  }
  defer fin.Close()

  par := new(parser.Parser)
  ast := par.Parse(bufio.NewReader(fin))
  env := environ.New()
  ast.Execute(env)
}

Օրինակ

Ստեղծենք test0.calc անունով ֆայլ և նրա մեջ գրենք մեր նոր ծրագրավորման լեզվով մի ծրագիր։ Այն օգտագործողից պահանջում է երկու թիվ և արտածում է այդ թվերի գումարը.

input a;
input b;
c = a+b;
print c

Սկզբնաղբյուրը։ http://code.google.com/p/calculator-with-big-integers/wiki/CalculatorII

Ծրագրի կոդը։ http://calculator-with-big-integers.googlecode.com/svn/tags/calc-ii/calcgo/

Հաշվարկիչից դեպի լեզվի ինտերպրետատոր, 9.8 out of 10 based on 10 ratings
Go Ծրագրավորման լեզուներ Ծրագրավորում Կոմպիլյատորներ Ուսումնական նյութեր
Previous StoryՄի քիչ վիքի-վիճակագրություն
Next StoryԻնչպե՞ս ստեղծել գրաֆիկ Մայքրասոֆթ Վորդ ծրագրում [տեսանյութ]

Comments: no replies

Join in: leave your comment Cancel Reply

(will not be shared)

Որոնում

Նշագրեր

*Nix-եր (18) android (17) C++ (19) C և C++ (27) Excel (10) html (10) Network Administration (16) System Administration (28) Windows 7 (14) Ալգորիթմներ (15) Անվտանգություն (29) ԳՆՈՒ/Լինուքս (16) Թեյնիկներին (57) Ժեշտ (44) Լակոնիկ (21) Լինուքս/Յունիքս հրամաններ (29) Լուսանկարչություն և մշակում (15) Խելախոսներ (19) Ծրագրավորման լեզուներ (16) Ծրագրավորում (64) Ծրագրեր (48) Հայականացում (28) Հումոր (11) Ուսումնական նյութեր (34) Սոցցանցային Հմտություններ (19) Վեբ (25) Վերլուծություն (10) Վորդպրես (21) ՏՏ և փիլիսոփայություն (21) Տվյալների բազաներ (12) Օպերացիոն համակարգեր (27) Օֆիսային ծրագրեր (22) անդրոիդ (16) բաշ (10) ինտերնետ (11) խելախոսներ (13) համացանց (15) հայատառ (10) հայերեն (11) հայերեն ստեղնաշար (11) հայկական սոֆթ (11) ստեղնաշար (10) սքրիփթ (14) վինդոուս (12) տեսանյութ (23)
Copyright ©2017 ThemeFuse. All Rights Reserved